JS、CSS以及img对DOMContentLoaded事件的影响


Posted in Javascript onAugust 12, 2014

前端的纯技术就是对规范的认知

什么是DOMContentLoaded事件?

首先想到的是查看W3C的HTML5规范,DOMContentLoaded事件在什么时候触发:

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事件。

接下来看看MDN上有关DOMContentLoaded事件的文档:

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.

这么看来,至少可以得出这么一个理论:DOMContentLoaded事件本身不会等待CSS文件、图片、iframe加载完成。
它的触发时机是:加载完页面,解析完所有标签(不包括执行CSS和JS),并如规范中所说的设置 interactive 和执行每个静态的script标签中的JS,然后触发。
而JS的执行,需要等待位于它前面的CSS加载(如果是外联的话)、执行完成,因为JS可能会依赖位于它前面的CSS计算出来的样式。

实践是检验真理的唯一标准

实验1:DOMContentLoaded事件不直接等待CSS文件、图片的加载完成

index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" type="text/css" href="./css/main.css" rel="external nofollow" rel="external nofollow" >
</head>
<body>
  <p>Content</p>
  <img src="./img/chrome-girl.jpg">
</body>
</html>

JS、CSS以及img对DOMContentLoaded事件的影响


图一

如果页面中没有script标签,DOMContentLoaded事件并没有等待CSS文件、图片加载完成。

Chrome开发者工具的Timeline面板可以帮我们记录下浏览器的一举一动。图一中红色小方框中的蓝线,表示DOMContentLoaded事件,它右边的红线和绿线分别表示load事件和First paint,鼠标hover在这些线露出灰色方框下面的一小部分时就会出现带有说明文字的tips(这交互够反人类的对吧!)。

实验2:DOMContentLoaded事件需要等待JS执行完才触发

index.html:

<!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" rel="external nofollow" rel="external nofollow" >
  <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:
console.timeStamp('External script after link in body');

JS、CSS以及img对DOMContentLoaded事件的影响

图二

如果页面中静态的写有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中添加的记录也可证明其触发时间。

JS、CSS以及img对DOMContentLoaded事件的影响


图三

现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)。先加载完成的资源,如果其依赖还没加载、执行完,就只能等着。

实验3:img何时开始解码、绘制?

从图三中我们可以发现一个有趣的地方:img的请求老早就发出了,但延迟了一段时间才开始解码。如图二、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到img加载结束,如图四所示,img是一边加载一边解码的:

JS、CSS以及img对DOMContentLoaded事件的影响


图三

现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)。先加载完成的资源,如果其依赖还没加载、执行完,就只能等着。

实验3:img何时开始解码、绘制?

从图三中我们可以发现一个有趣的地方:img的请求老早就发出了,但延迟了一段时间才开始解码。如图二、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到img加载结束,如图四所示,img是一边加载一边解码的:

JS、CSS以及img对DOMContentLoaded事件的影响


图四

抱着“猜想——验证”的想法,我猜想这是因为img这个资源是否需要展现出来,需要等 所有的JS和CSS的执行完 才知道,因为main.js可能会执行某些DOM操作,比如删除这个img元素,或者修改其src属性,而CSS可能会将其 display: none

JS、CSS以及img对DOMContentLoaded事件的影响


图五

JS、CSS以及img对DOMContentLoaded事件的影响


图六

JS、CSS以及img对DOMContentLoaded事件的影响


图七

图五中没有JS和CSS,img的数据一接收到就马上开始解码了。
图六中没有JS,但img要等到CSS加载完才开始解码。
图七的代码跟图六的代码唯一的区别是CSS把img给 display: none; ,这使得img虽然请求了,但根本没有进行解码。
这说明,img是否需要解码、绘图(paint)出来,确实需要等CSS加载、执行完才能知道。也就是说,CSS会阻塞img的展现!那么JS呢?

JS、CSS以及img对DOMContentLoaded事件的影响


图八

图八对应的代码:

<!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模拟慢速网络

Javascript 相关文章推荐
Javascript对象中关于setTimeout和setInterval的this介绍
Jul 21 Javascript
js自定义方法通过隐藏iframe实现文件下载
Feb 21 Javascript
javascript中注册和移除事件的4种方式
Mar 20 Javascript
什么是cookie?js手动创建和存储cookie
May 27 Javascript
javascript轻量级模板引擎juicer使用指南
Jun 22 Javascript
js对象的复制继承实例
Jan 10 Javascript
BootStrap组件之进度条的基本用法
Jan 19 Javascript
使用JS代码实现俄罗斯方块游戏
Aug 03 Javascript
VeeValidate 的使用场景以及配置详解
Jan 11 Javascript
vue router总结 $router和$route及router与 router与route区别
Jul 05 Javascript
一篇文章看懂JavaScript中的回调
Jan 05 Javascript
vue3.0 项目搭建和使用流程
Mar 04 Vue.js
更快的异步执行(setTimeout多浏览器)
Aug 12 #Javascript
JavaScript Promise启示录
Aug 12 #Javascript
深入理解Javascript中this的作用域
Aug 12 #Javascript
javascript实现在某个元素上阻止鼠标右键事件的方法和实例
Aug 12 #Javascript
JavaScript弹出窗口方法汇总
Aug 12 #Javascript
Javascript中3种实现继承的方法和代码实例
Aug 12 #Javascript
jQuery判断checkbox是否选中的3种方法
Aug 12 #Javascript
You might like
《PHP编程最快明白》第六讲:Mysql数据库操作
2010/11/01 PHP
页面乱码问题的根源及其分析
2013/08/09 PHP
php使用event扩展的io复用测试的示例
2020/10/20 PHP
分享一个自定义的console类 让你不再纠结JS中的调试代码的兼容
2012/04/20 Javascript
ie6下png图片背景不透明的解决办法使用js实现
2013/01/11 Javascript
使用apply方法实现javascript中的对象继承
2013/12/16 Javascript
javascript中的原型链深入理解
2014/02/24 Javascript
jQuery实现仿QQ在线客服效果的滚动层代码
2015/10/15 Javascript
JavaScript学习笔记之创建对象
2016/03/25 Javascript
浅析JavaScript函数的调用模式
2016/08/10 Javascript
jQuery实现的多张图无缝滚动效果【测试可用】
2016/09/12 Javascript
js阻止冒泡和默认事件(默认行为)详解
2016/10/20 Javascript
JavaScript原生数组Array常用方法
2017/04/06 Javascript
shiro授权的实现原理
2017/09/21 Javascript
vue中使用ueditor富文本编辑器
2018/02/08 Javascript
推荐一个基于Node.js的表单验证库
2019/02/15 Javascript
vue 项目build错误异常的解决方法
2019/04/22 Javascript
详解JavaScript 新语法之Class 的私有属性与私有方法
2019/04/23 Javascript
[00:32]10月24、25日 辉夜杯外卡赛附加赛开赛!
2015/10/23 DOTA
[01:10:03]OG vs EG 2018国际邀请赛淘汰赛BO3 第三场 8.23
2018/08/24 DOTA
[03:12]完美世界DOTA2联赛PWL DAY6集锦
2020/11/05 DOTA
Python中pip安装非PyPI官网第三方库的方法
2015/06/02 Python
Python DataFrame设置/更改列表字段/元素类型的方法
2018/06/09 Python
Python面向对象之类和对象实例详解
2018/12/10 Python
解决python 3 urllib 没有 urlencode 属性的问题
2019/08/22 Python
pycharm-professional-2020.1下载与激活的教程
2020/09/21 Python
python3中celery异步框架简单使用+守护进程方式启动
2021/01/20 Python
使用CSS3制作一个简单的进度条(demo)
2017/05/23 HTML / CSS
用html5实现语音搜索框的方法
2014/03/18 HTML / CSS
土耳其时尚购物网站:Morhipo
2017/09/04 全球购物
在线学习西班牙语、法语或其他语言:Babbel.com
2018/02/07 全球购物
自荐书范文
2013/12/08 职场文书
运动员获奖感言
2014/08/15 职场文书
一份关于丢失公司财物的检讨书
2014/09/19 职场文书
微信小程序用户授权最佳实践指南
2021/05/08 Javascript
SpringCloud中分析讲解Feign组件添加请求头有哪些坑梳理
2022/06/21 Java/Android