JS、CSS以及img对DOMContentLoaded事件的影响
最近在做性能有关的数据上报,发现了两个非常有意思的东西:Chrome开发者工具的Timeline分析面板,以及DOMContentLoaded事件。一个是强大的令人发指的性能分析工具,一个是重要的性能指标,于是就用Timeline对DOMContentLoaded事件进行了一番研究。
前端的纯技术就是对规范的认知
什么是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:
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的请求老早就发出了,但延迟了一段时间才开始解码。如图二、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到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模拟慢速网络
原创文章转载请注明:
转载自AlloyTeam:http://www.alloyteam.com/2014/03/effect-js-css-and-img-event-of-domcontentloaded/
JS、CSS以及img对DOMContentLoaded事件的影响的更多相关文章
- JS/CSS/IMG加载顺序关系之DOMContentLoaded事件
DOMContentLoaded介绍 DOMContentLoaded事件的触发条件是: 将会在“所有的DOM全部加载完毕并且JS加载执行后触发”. 但如果“js是通过动态加载进来的话,是不会影响到D ...
- load/domContentLoaded事件、异步/延迟Js 与DOM解析
一.DOMContentLoaded 与 load事件 关于load和DOMContentLoaded事件,mdn对于它们是这样描述的: DOMContentLoaded mdn文档地址:https: ...
- DOMContentLoaded事件中使用异步
概述 我在之前的博文(Performance面板看js加载)中提到过,如果利用监听DOMContentLoaded事件的方式来加载js是不能优化加载的,不能够替代jquery中的ready方法,原因是 ...
- 深入理解Javascript封装DOMContentLoaded事件
最近在写一个Javascript的框架,刚把DOMContentLoaded事件封装好,略带小兴奋,把开发过程中遇到的原理和兼容性问题做篇笔记,省的忘记到处找. 我们在写js代码的时候,一般都会添加w ...
- js和css文件位置对页面性能的影响
翻译了一篇Performance上的关于页面性能的文章<DecIPhering the critical rendering path>,原文在这里.需要进一步整理和了解有关js.css等 ...
- electron之Windows下使用 html js css 开发桌面应用程序
1.atom/electron github: https://github.com/atom/electron 中文文档: https://github.com/atom/electron/tree ...
- 勤能补挫-简单But易错的JS&CSS问题总结
错误频率较高的JS&CSS问题 勤能补拙,不管是哪门子技术,在实践中多多总结,开发效率慢慢就会提升.本篇介绍几个经常出错的JS&CSS问题,包括事件冒泡.(使用offset.scrol ...
- 实用js+css多级树形展开效果导航菜单
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- DOMContentLoaded事件
今天查看百度空间源代码,发现多了个util.js文件,打开看看.里面里面定义了addDOMLoadEvent.这是干什么用的? 仔细查看代码,发现在Mozilla添加了DOMContentLoaded ...
随机推荐
- 用C#通过反射实现动态调用WebService 告别Web引用(转载)
我们都知道,调用WebService可以在工程中对WebService地址进行WEB引用,但是这确实很不方便.我想能够利用配置文件灵活调用WebService.如何实现呢? 用C#通过反射实现动态调用 ...
- mysql和redis加入到windows服务
mysql加入到windows服务: mysqld --install Mysql5.6 mysqld --remove mysql5.6 从windows的服务中删除mysql服务 net st ...
- ES(3): ES Cluster Extended Azure Storage
Azure VM的磁盘空间远远不能满足ES集群存储需求(还需除掉VM的临时盘),同时也未找着ES配置 block blob storage 存储的组件,因此下文介绍通过挂载附加盘的方式增加ES集群存储 ...
- 【appium】根据class_name定位元素
目前没有尝试成功,等成功后补充 class_name=class可以通过UIAutomatorViewer获得.
- [转]oracle导入提示“IMP-00010:不是有效的导出文件,头部验证失败”的解决方案
这是由于导出的dmp文件与导入的数据库的版本不同造成的用Notepad++查看了dmp文件,在头部具修改成你将导入目标数据库的版本号以下对应的版本号: 11g R2:V11.02.00 11g R1: ...
- RPC终结点映射
“没有更多的终结点可用”错误消息表示 RPC 终结点映射程序无法对基于 RPC 运行的服务使用大于 1024 的端口.注意:RPC 终结点映射程序在端口 135 上运行. http://support ...
- LwIP:处理链路状态改变
[文/告别年代 Email:byeyear@hotmail.com] 重大修订记录 ----------------------------------------- 2016.11.03 感谢@ ...
- bzoj1864 三色二叉树
Description Input 仅有一行,不超过500000个字符,表示一个二叉树序列. Output 输出文件也只有一行,包含两个数,依次表示最多和最少有多少个点能够被染成绿色. 记录每个节点染 ...
- php 表单提交方法
1.收集 HTML 表单提交的数据 ,PHP $_REQUEST 用于收集 HTML 表单提交的数据. <!DOCTYPE html><html><body> &l ...
- aapium 设置安卓机参数
例子: class iBer(Unittest.TestCase): @classmethod def setUpClass(cls): logger=public.log() desired_cap ...