[译] 什么阻塞了 DOM?

原文地址:https://www.keycdn.com/blog/blocking-the-dom/
原文作者:BRIAN JACKSON



当我们谈到web性能或者优化页面级别的速度时,非常重要的一点是要理解HTML和一个页面是如何在浏览器中构造的,这样你才能找到由于渲染阻塞导致的页面加载延迟。在这篇文章中,我们会深入了解是 什么阻塞了DOM 以及你应该怎样避免这种情况。

什么是DOM?

DOM是Document Object Model(文档对象模型)的缩写。它是为HTML和XML定义的一个编程接口,提供了文档的结构化表示(节点树状结构),同时也规定了使用脚本编程语言(例如JavaScript)应该如何访问以及操作DOM。这样一个节点树状结构是由不同的元素、父节点、子节点、兄弟节点等构成,它们彼此都有层级化的关系。下图是一个HTML DOM的例子:

HTML DOM

用人话描述DOM

简单的讲,当你使用一个类似Chrome开发者工具的东东时,你可以看到一个可视化的DOM。你的HTML并不是DOM,但Chrome开发者工具为你展现了一个经过HTML或JavaScript加工之后的DOM。所以你可以把DOM理解成解析后的HTML。

Chrome开发者工具中体现的DOM

什么在阻塞DOM?

当我们分析页面速度时,我们总要考虑什么阻塞了DOM导致我们的页面加载出现延迟。这些阻塞因素我们可以叫做 阻塞渲染的资源 ,例如 HTML、CSS(也包括web font)和 JavaScript。

要查看什么阻塞了DOM的最简单的方法之一就是使用 Chrome开发者工具 (Chrome DevTools) 和Google的 PageSpeed Insight。在下面的例子中,我们使用了最新的Chrome开发者工具 (可以通过 Chrome Canary 获得)。

  1. 在Chrome中启动开发者工具

    • Windows:F12 或者 Ctrl + Shift + I
    • Mac: Cmd + Opt + I
  2. 切换到 Network (网络)面板,刷新页面( Win: Ctrl + R, Mac: Cmd + R
  3. 现在你会看到一个加载时间瀑布图。这里有两个值得我们关注的东东:第一个是 DOMContentLoaded 是384ms(译者注:原文如此,看图的话应该是281ms),第二个就是瀑布图中的在蓝线之前的绿色部分(译者注:原图有点问题,蓝线看起来是紫色的)

我们知道CSS和JavaScript都是阻塞渲染的资源,它们都会在蓝色的DOMContent之前加载。请注意,图像是不会阻塞渲染的 ,所以如果有图像落在蓝线之前或之上你可以放心的忽略掉,当然优化图像也是很重要的一项工作。在这个例子里面,我们可以看到 style.css 和 jquery.min.js 都是阻塞渲染的资源。

Chrome开发者工具的网络面板中的加载时间瀑布图

你同样可以通过 Google PageSpeed Insightshttps://developers.google.com/speed/pagespeed/insights/) 工具来验证我们上面的结论。下图中显示这两个文件的确是阻塞渲染的。

Google PageSpeed Insights

我们下面要学习的是如何 通过优化关键渲染路径来避免CSS和JavaScript阻塞DOM 。尽管HTML也算是一个阻塞渲染的资源(译者注:记住HTML不是DOM),但DOM是可以增量构建的(译者注:所以优化的是CSS和JavaScript,而不是HTML)。

注意,我们无需追求在 Google PageSpeed Insights 的 100/100。例如,如果你链接引用了Google的web字体,那么无论你做什么,这个外部的 fonts.googleapis.com 样式都始终会是一个阻塞渲染的资源。重要的是当你在处理有着10+个阻塞渲染资源的一个大型站点时,要理解清楚什么导致了延迟,有什么样的策略可以使这些资源可以更有效率的加载。

CSS

非渲染阻塞的CSS

如果你追求一个完全没有阻塞的CSS,那么你的唯一选项就是:在HTML中内联嵌入你的CSS。你可以把需要初始渲染的CSS,一般来讲就是第一屏的样式,直接放在 HEAD 里面的 <style></style> 中,然后剩下的CSS放在 </body> 之前。这样做可以完全避免CSS阻塞渲染。

有几个可以辅助你完成内联样式嵌入的自动化插件

你当然也可以使用JavaScript来加载CSS,但是这样做会导致页面在加载结束时重绘,因此这个选项对于网站访问者来说不一定会很理想。

在Chrome开发者工具中可以看到,我们做完内联样式优化之后的版本中 DOMContentLoaded 减少到了 278ms

内联优化后的版本

现在我们再去 Google PageSpeed Insights 测试,会发现CSS已不是阻塞渲染的资源了。

内联优化后的测试

当然这很不错,但一切取决于你的站点实际情况。大多数站点并不想内联嵌入所有的CSS,因为CSS的内容多少直接且显著的影响了页面下载的大小。对于小型站点或者就是个 Landing Page ,这种情况下内联嵌入CSS可以是一个不错的选项,如果你真的想完全避免CSS阻塞渲染的话。

我们的CSS建议

即使在我们自己的KeyCDN主页上,我们也有一个阻塞渲染的CSS。但是,我们做了其他一些事情来优化CSS的加载时间,下面是我们的建议:

  1. 正确的调用你的CSS文件 (译者注:原文如此,感觉应该是位置或时机?)
  2. 使用 media queries (媒体查询) 来标记某些CSS为非阻塞资源 (译者注: 比如 <link href="other.css" rel="stylesheet" media="(min-width: 40em)"> 这样可以在其他屏幕尺寸加载时就不用加载这个css了)
  3. 减少CSS的数量(尽可能放到一个CSS文件中)
  4. Minify CSS文件(删除多余的空格、字符、注释等)
  5. 尽可能的减少样式数量(译者注:和第三条不同,是减少样式数量,不是文件数量)

一些用于最小化(Minify)CSS的工具

JavaScript

非渲染阻塞的JavaScript

有一些关于JavaScript的最佳实践需要牢记在心:

  1. 把脚本放在页面尾部 </body> 之前的位置
  2. 使用async或defer指令来避免阻塞渲染

异步加载JavaScript

async 允许脚本在后台下载,因此是无阻塞的。但是当下载完成的时刻,渲染又会阻塞了,这是因为脚本执行了。当脚本执行完毕,渲染又恢复了。

 <script async src="foobar.js"></script>

延迟加载JavaScript

defer 指令做的事情和 async 基本一样,区别点在于 defer 是严格要求脚本的执行顺序必须和在HTML中标记的顺序一样。所以说,可能存在一种情况,当一些脚本已经下载完毕,这些脚本不会立即执行,它们会等待其他脚本下载完成,因为那些脚本在HTML中出现在它们之前。

Patrick Sexton写了一篇非常好的博文: 如何延迟加载JavaScript

除去上面两条,我们对于JavaScript的另外3条建议是:

  1. 减少JavaScript的数量(尽量整合成一个JS文件)
  2. Minify(最小化)JavaScript
  3. 如果JavaScript很小的话,可以内联嵌入

用于最小化JavaScript的自动化任务插件

  • Grunt:grunt-contrib-uglify
  • Gulp:gulp-uglify

    通过把我们的JavaScript移动到页面尾部,以及使用 async 指令之后,我们把 DOMContentLoaded 显著的减少到了 144ms。我们可以看到 jquery.min.js 文件现在出现在了DOM的蓝线之后了。

JavaScript优化后在Chrome开发者工具中的表现

那么在 Google PageSpeed Insights 中同样的,由于我们已经异步加载了JavaScript,所以这一项的扣分不存在了,我们达成了 100/100 。

达成100分

Web Fonts

Web Fonts(Web字体)也被视为一种阻塞渲染的资源,因为它们是通过CSS加载的。你有两个选择:阻塞渲染或者延迟重绘(这种情况你需要处理 FOUT)。举个例子,在Chrome(36以上版本),Opera(23以上版本)和Firefox中有一个 three-second timeout,在超时后,fall-back字体会被使用。

同样的我们有几个关于加载字体和优化关键渲染路径的小建议:

  1. 使用Web Font加载器或者字体加载API
  2. 使用内联嵌入优化字体加载
  3. 使用例如localStorage等存储方法

关于更多深入的加载Web Fonts、如何避免渲染阻塞以及FOUT/FOIT等可以查看这篇博文:
analyzing web font performance

小总结

我们希望到这里你可以对阻塞DOM、DOM树是如何构建的,为何会被CSS和JavaScript阻塞等问题有了一些了解。请再次记住不要追求 Google PageSpeed Insights 的 100/100,重要的是理解你的渲染阻塞资源是如何阻塞DOM的以及你会怎样正确的优化使得页面的加载变快。

时间: 2024-10-10 03:35:58

[译] 什么阻塞了 DOM?的相关文章

JavaScript - 收藏集 - 掘金

Angular 中的响应式编程 -- 浅淡 Rx 的流式思维 - 掘金第一节:初识Angular-CLI第二节:登录组件的构建第三节:建立一个待办事项应用第四节:进化!模块化你的应用第五节:多用户版本的待办事项应用第六节:使用第三方样式库及模块优化用第七节:给组件带来活力Rx--隐藏在 Angular 中的利剑Redux你的 A... Electron 深度实践总结 - 前端 - 掘金思维导图 前言: Electron 从最初发布到现在已经维护很长一段时间了,但是去年才开始慢慢升温.笔者个人恰好

原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

hello~各位亲爱的看官老爷们大家好.估计大家都听过,尽量将CSS放头部,JS放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂.因此洗(wang)心(yang)革(bu)面(lao),小结一下最近玩出来的成果. 友情提示,本文也是小白向为主,如果直接想看结论可以拉到最下面看的~ 由于关系到文件的读取,那是肯定需要服务器的,我会把全部的文件放在github上,给我点个 star 我会开心

CSS 与 JS 是这样阻塞 DOM 解析和渲染的

估计大家都听过,尽量将 CSS 放头部,JS 放底部,这样可以提高页面的性能.然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂.因此洗(wang)心(yang)革(bu)面(lao),小结一下最近玩出来的成果. 友情提示,本文也是小白向为主,如果直接想看结论可以拉到最下面看的~ node 端唯一需要解释一下的是这个函数: function?sleep(time)?{??return?new?Promise(functi

异步执行js脚本——防止阻塞

JS允许我们修改页面中的所有方面:内容,样式和用户进行交互时的行为. 但是js同样可以阻塞DOM树的形成并且延迟页面的渲染. 让你的js变成异步执行,并且减少不必要的js文件从而提高性能. JavaScript可以查询和修改DOM和CSSOM JavaScript的执行阻塞了CSSOM的执行 JavaScript 阻塞了DOM的形成,除非特殊声明js异步执行 js是一个同步语言可以修改网页的任何方面: <html> <head> <meta name="viewpo

浏览器渲染-css阻塞

引用 由于关系到文件的读取,那是肯定需要服务器的,我会把全部的文件放在github上 node端唯一需要解释一下的是这个函数: function sleep(time) { return new Promise(function(res) { setTimeout(() => { res() }, time); }) } 嗯!其实就延时啦.如果CSS或者JS文件名有sleep3000之类的前缀时,意思就是延迟3000毫秒才会返回这文件. 下文使用的HTML文件是长这样的: 12345678910

编码、解码形成DOM树的过程

浏览器从网络或硬盘中获得HTML字节数据后会经过一个流程将字节解析为DOM树: 编码: 先将HTML的原始字节数据转换为文件指定编码的字符. 令牌化: 然后浏览器会根据HTML规范来将字符串转换成各种令牌(如<html>.<body>这样的标签以及标签中的字符串和属性等都会被转化为令牌,每个令牌具有特殊含义和一组规则).令牌记录了标签的开始与结束,通过这个特性可以轻松判断一个标签是否为子标签(假设有<html>与<body>两个标签,当<html>

【转】JavaScript 的装载和执行

承接前面一篇文章<浏览器的渲染原理简介> ,本文来说下JavaScript的装载和执行. 通常来说,浏览器对于 JavaScript 的运行有两大特性: 1) 载入后马上执行 2) 执行时会阻塞页面后续的内容(包括页面的渲染.其他资源的下载) 所以,如果有多个JS文件被引入,那么对于浏览器来说,这些JS文件将被串行地载入并依次执行. 由于JavaScript 可能会操作 HTML文档的DOM 树,所以浏览器一般都不会像并行下载CSS文件一样并行下载JS文件,这是JS文件的特殊性造成的.因此,如

js twe

一.函数1.首先了解了函数的定义,函数的定义有两种方式:----(1)命名函数:function name(){ 执行语句 }----(2)匿名函数:var name = function(){ 执行语句 } ps:Function是js的原生对象. 2.学习了函数的定义,就要了解如何执行函数,执行函数也有两种方式:----(1)直接使用:function name(){ console.info("执行函数"); }execute();----(2)赋值使用:function fun

Javascript 装载和执行

转自:http://coolshell.cn/articles/9749.html#more-9749 一两个月前在淘宝内网里看到一个优化Javascript代码的竞赛,发现有不少的人对Javascript的执行和装载的基础并不懂,所以,从那天起我就想写一篇文章,但一直耽搁了.自上篇<浏览器渲染原理简介>,正好也可以承前启后. 首先,我想说一下Javascript的装载和执行.通常来说,浏览器对于Javascript的运行有两大特性:1)载入后马上执行,2)执行时会阻塞页面后续的内容(包括页面