破译关键渲染路径(翻译)

破译关键渲染路径(翻译)

英文来源:http://calendar.perfplanet.com/2012/deciphering-the-critical-rendering-path/
参考:https://developers.google.com/web/fundamentals/performance/critical-rendering-path/analyzing-crp
translated by 南洋

前言 By 南洋

最近在研究页面性能监控的事情,得知HTML5的 Navigation Timing API可以获取到一系列加载的关键时间节点。

1
2
3
4
5
6
7
'use strict'
console.log(`performance.timing.domInteractive: ${performance.timing.domInteractive}`)
console.log(`performance.timing.domContentLoadedEventStart: ${performance.timing.domContentLoadedEventStart}`)
console.log(`performance.timing.domContentLoadedEventEnd: ${performance.timing.domContentLoadedEventEnd}`)
console.log(`performance.timing.domComplete: ${performance.timing.domComplete}`)
console.log(`performance.timing.loadEventStart: ${performance.timing.loadEventStart}`)
console.log(`performance.timing.loadEventEnd: ${performance.timing.loadEventEnd}`)

将这些代码拷贝到console中,就可以看到加载过程中的部分数据,或直接查看performance.timing 我暂时为监控安排的数据为

  • 白屏时间
  • 用户可交互时间
  • 总加载时间
  • 首屏时间

白屏时间跟总加载时间暂时可以在这个API直接获得,有分歧的是用户可交互时间,不知道怎么通过这个API取得,在阅读了上面这篇文章后就悟了。

在文档中只存在异步脚本的不适用
interactingTime(用户可交互时间) = timing.domContentLoadedEventEnd - timing.navigationStart

另外需要提前说明的是:在下文中出现了一张浏览器加载过程中的路径图,上面的的各种英文代表的是document的状态,什么时间点,浏览器完成了什么事,状态就会相应的改变,而下文中经常提到的DOMContentLoaded(DCL)window.onload类似,是事件,当document的状态相应变化时,就会触发相应的事件。而图中的performance.timing.domContentLoadedEventStartperformance.timing.domContentLoadedEventEnd之差就代表了DOMContentLoaded事件执行的时间。同理performance.timing.loadEventStartperformance.timing.loadEventEnd之差代表了onload事件执行的时间。

正文开始

正如steve在先前的文章中提到的,window.onload不是测量网站速度的最好方法。这个方法是最方便的也是最熟悉的,但是这并不能够获得一些现代页面的一些动态速度信息。我们想象用户能察觉到页面的一种表现:页面开始加载后多久后用户能跟页面进行交互?
交互的定义取决于你的页面,对于有些页面我们能获得可见的字就算是交互,而对于另外一些页面,写了狠多的js组件去构建UI(类似Gmail)。对于这两种例子,他们有一个共同点,那就是用户必须能看见的这个页面,换句话来讲就是浏览器需要呈现什么东西到屏幕上。
带着这个问题我们来探究一下,当第一批内容呈现在现代浏览器上的时候,到底发生了什么事情。

DOM + CSSOM = Render Tree

渲染管道的准确时间点和行为当然会取决于解析(parsing)布局(layout)还有一些管道的结合,抛开不同点来讲,为了得到一些可见的东西在屏幕上,所有浏览器必须要构建什么东西到渲染书(render tree)
Alt text
HTML文档的解析构建成了DOM树,在平行空间里有一个被遗忘了的兄弟—-CSSOM,CSSOM是有一些样式规则和样式资源所构建的,DOM树和CSSOM结合构成渲染树(render tree)。这时标明了浏览器有足够的信息区形成布局然后绘制在屏幕上,到目前为止,一切顺利。
然而,上图的例子是最乐观的,CSSOM和DOMtree相安无事的在两个平行空间里被构建。接下来很不幸的我将介绍我们最爱的朋友—javascript。

  • 同步的javascript可以改些文档在任何节点,因此DOMtree 一旦碰上script标签就会停止构建
  • javascript能查询dom对象的可被计算的样式,这意味着js可以阻塞css

Alt text

不像上面那种DOM和CSSDOM相安无事的个子构建,现在两者潜在地互相牵制:DOMtree无法构建直到javascript被执行,javascript无法执行直到CSSOM是可行的,Yikes。。。

Depending on how this dependency graph is resolved on your pages, which is governed by how, and how many resources you include in that first “critical path” of the page load, the time to first render will vary accordingly. Can we get some metrics, or insights into this process? Turns out, yes we can!

Document Interactive & DOMContentLoaded

HTML5 定义了一系列浏览器从开始工作请求http到呈现页面必须执行的规则well documented sequence of steps.
Alt text

浏览器加载关键路径图
特别的,一系列步骤的较后方两个步骤能解决我们最开始提到的问题。在页面最开始的最开始的内容的出现之前,到底发生了什么事。

  • document被标记为interactive状态,当浏览器结束DOMtree的构建,标明,DOMtree准备好了。
  • 一旦任何标有defer的script标签被执行了,浏览器就会触发了DOMContentLoaded事件,这个时候没有样式文件阻塞了脚本的执行(我注:当然这个script标签是在interactive状态之后的,如果没有表有defer的script,一旦document的状态变成interactive,就会触发DOMContentLoaded事件。)

如果没有同步的javascript在文档中,那么DOMtree很CSSOM的构建会平行的进行,当我们将javascript请入的时候事情会变的有趣很多。

如果你给一个同步脚本加上defer,那么这个脚本就会解除对DOMtree构建的阻塞,document的interactive状态不会等待脚本的执行就会开启,值得注意的是,这个相同的脚本会在DOMContentLoaded事件触发之前执行。然后脚本开始执行,简而言之,给脚本加defer,脚本不会阻塞document interactive这个状态了,但是依然会组织DCL事件的触发。(自注:defer执行的时间)

如果你给一个同步脚本加上async异步,CSSOM不会阻塞脚本的执行了,最重要的一个区别,DCL事件的触发不需要等待异步脚本的执行!
(自注:这就意味着DCL事件的触发不需要等待CSSOM的完成,此时的现象就跟Develop文档里的第一段解释一样了,同样,这个现象也符合文档中没有脚本的情况https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded
The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.

第一个关键点:默认情况下,javascript会阻塞DOMtree的构建,也许是被CSSOM构建花费时间的阻塞(自注:CSSOM的构建导致javascript的阻塞,从而导致DOMtree的构建)。同步的脚本很坏,但是我们早就知道,让脚本加上defer/async相当于对解析器做了个保证,这些脚本不会重写dom,故而被放行。

第二个关键点: 如果在某种情况下我们必须要等待脚本的执行,我们首先要让CSSOM构建完毕。换句话说,javascript跟css之间有严重的依赖关系。。。。样式在上,脚本在下,现在知道原因了?

好了!理论固然重要,但是这些抽象的知识能帮助我们优化页面速度吗?balabala。。废话。。(理论是基础)

沿着你的页面的关键路劲

如果不出意外,监控documenr interactive状态,会给我们很好的一个指示:我们是否因为同步的脚本阻塞了DOMtree的构建,有时候测量这个阻塞的行为没有其他办法了,所以监控interactive状态会是一个折中的好办法。

而DCL事件(domContentLoaded)也是个关键的指标,许多著名的库,类似jquery,在触发这个事件后开始执行他们的代码,换句话说,这可能是用户能跟页面交互的一个开始时间节点,而在这个事件中的回调,也是在这个事件触发后发给用户。如果你做对了工作,通过逐步的提高,你可以让页面的速度加快,因此用户可以跟页面交互而浏览器继续加载剩余的资源(img等)。IE团队有一个绝佳的例子演示了DCL与window.onload的不同。
http://ie.microsoft.com/testdrive/HTML5/DOMContentLoaded/Default.html

完。

完。


原创文章,转载请注明出处!