最近在作性能有关的数据上报,发现了两个很是有意思的东西:Chrome开发者工具的Timeline分析面板,以及DOMContentLoaded事件。一个是强大的使人发指的性能分析工具,一个是重要的性能指标,因而就用Timeline对DOMContentLoaded事件进行了一番研究。javascript
前端的纯技术就是对规范的认知
什么是DOMContentLoaded事件?css
首先想到的是查看W3C的HTML5规范,DOMContentLoaded事件在何时触发:html
Once the user agent stops parsing the document, the user agent must run the following steps:
1. Set the current document readiness to “interactive” and the insertion point to undefined.
Pop all the nodes off the stack of open elements.
2. If the list of scripts that will execute when the document has finished parsing is not empty, run these substeps:
2.1 Spin the event loop until the first script in the list of scripts that will execute when the document has finished parsing has its “ready to be parser-executed” flag set and the parser’s Document has no style sheet that is blocking scripts.
2.2 Execute the first script in the list of scripts that will execute when the document has finished parsing.
2.3 Remove the first script element from the list of scripts that will execute when the document has finished parsing (i.e. shift out the first entry in the list).
2.4 If the list of scripts that will execute when the document has finished parsing is still not empty, repeat these substeps again from substep 1.
3. Queue a task to fire a simple event that bubbles named DOMContentLoaded at the Document.前端
规范老是那么的晦涩,但至少有一点是能够明确了的,就是在JS(不包括动态插入的JS)执行完以后,才会触发DOMContentLoaded事件。html5
接下来看看MDN上有关DOMContentLoaded事件的文档:java
The DOMContentLoaded event is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading
Note: Stylesheet loads block script execution, so if you have a<script>
after a<link rel="stylesheet" ...>
, the page will not finish parsing – and DOMContentLoaded will not fire – until the stylesheet is loaded.node
这么看来,至少能够得出这么一个理论:DOMContentLoaded事件自己不会等待CSS文件、图片、iframe加载完成。
它的触发时机是:加载完页面,解析完全部标签(不包括执行CSS和JS),并如规范中所说的设置interactive
和执行每一个静态的script标签中的JS,而后触发。
而JS的执行,须要等待位于它前面的CSS加载(若是是外联的话)、执行完成,由于JS可能会依赖位于它前面的CSS计算出来的样式。chrome
实践是检验真理的惟一标准
实验1:DOMContentLoaded事件不直接等待CSS文件、图片的加载完成
index.html:windows
1
2
3
4
5
6
7
8
9
10
11
12
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="./css/main.css">
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
</body>
</html>
|
若是页面中没有script标签,DOMContentLoaded事件并无等待CSS文件、图片加载完成。
Chrome开发者工具的Timeline面板能够帮咱们记录下浏览器的一举一动。图一中红色小方框中的蓝线,表示DOMContentLoaded事件,它右边的红线和绿线分别表示load事件和First paint,鼠标hover在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的tips(这交互够反人类的对吧!)。
实验2:DOMContentLoaded事件须要等待JS执行完才触发
index.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
console.timeStamp('Inline script before link in head');
window.addEventListener('DOMContentLoaded', function(){
console.timeStamp('DOMContentLoaded event');
});
</script>
<link rel="stylesheet" type="text/css" href="./css/main.css">
<script type="text/javascript">
console.timeStamp('Inline script after link in head');
</script>
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
<script type="text/javascript" src="./js/main.js"></script>
</body>
</html>
|
main.js:
1
|
console.timeStamp('External script after link in body');
|
图二
若是页面中静态的写有script标签,DOMContentLoaded事件须要等待JS执行完才触发。
而script标签中的JS须要等待位于其前面的CSS的加载完成。
console.timeStamp()
能够向Timeline中添加一条记录,并对应上方的一条黄线。
从图二中能够看出,在CSS以前的JS马上获得了执行,而在CSS以后的JS,须要等待CSS加载完后才执行,比较明显的是main.js早就加载完了,但仍是要等main.css加载完才能执行。而DOMContentLoaded事件,则是在JS执行完后才触发。滑动Timeline面板中表示展现区域的滑块,如图三,放大后便可看到表示DOMContentLoaded事件的蓝线(以前跟黄线和绿线靠的太近了),固然,经过console.timeStamp()
向TimeLine中添加的记录也可证实其触发时间。
现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,可是,执行CSS和JS的顺序仍是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)。先加载完成的资源,若是其依赖还没加载、执行完,就只能等着。
实验3:img什么时候开始解码、绘制?
从图三中咱们能够发现一个有趣的地方:img的请求老早就发出了,但延迟了一段时间才开始解码。如图2、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到img加载结束,如图四所示,img是一边加载一边解码的:
抱着“猜测——验证”的想法,我猜测这是由于img这个资源是否须要展示出来,须要等 全部的JS和CSS的执行完 才知道,由于main.js可能会执行某些DOM操做,好比删除这个img元素,或者修改其src属性,而CSS可能会将其 display: none
。
图五中没有JS和CSS,img的数据一接收到就立刻开始解码了。
图六中没有JS,但img要等到CSS加载完才开始解码。
图七的代码跟图六的代码惟一的区别是CSS把img给 display: none;
,这使得img虽然请求了,但根本没有进行解码。
这说明,img是否须要解码、绘图(paint)出来,确实须要等CSS加载、执行完才能知道。也就是说,CSS会阻塞img的展示!那么JS呢?
图八对应的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
console.timeStamp('Inline script in head');
window.addEventListener('DOMContentLoaded', function(){
console.timeStamp('DOMContentLoaded event');
});
</script>
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
<script type="text/javascript" src="./js/main.js"></script>
</body>
</html>
|
很是使人惊讶,在有JS而没有CSS的页面中,img竟然可以在收到数据后就马上开始解码、绘图(paint),也就是说,JS并无阻塞img的展示!这跟咱们之前理解的JS会阻塞img资源的传统观念不太同样,看来Chrome对img的加载和展示作了新的优化。
咱们经常使用的jQuery的 $(document).ready()
方法,就是对DOMContentLoaded事件的监听(固然,其内部还会经过模拟DOMContentLoaded事件和监听onload事件来提供降级方案)。一般推荐在DOMContentLoaded事件触发的时候为DOM元素注册事件。因此尽快的让DOMContentLoaded事件触发,就意味着可以尽快让页面可交互:
- 减少CSS文件体积,把单个CSS文件分红几个文件以并行加载,减小CSS对JS的阻塞时间
- 次要的JS文件,经过动态插入script标签来加载(动态插入的script标签不阻塞DOMContentLoaded事件的触发)
- CSS中使用的精灵图,能够利用对img的预加载,放在html中跟CSS文件一块儿加载
在作实验的过程当中,感受Chrome开发者工具的Timeline面板很是强大,浏览器的一举一动都记录下来。之前咱们前端开发要想理解、探索浏览器的内部行为,或者摸着石头过河的作黑盒测试,或者事倍功半的研究浏览器源码,惟一高效点的作法就是学习别人的研究经验,看老外的文章,但浏览器的发展突飞猛进(好比此次实验发现的JS不阻塞img的展示),别人的经验始终不是最新、最适合的,关键是要结合本身的业务、需求场景,有针对性的作分析和优化。
PS.
以上测试环境为windows/chrome,并用Fiddler模拟慢速网络