1前提
从url到dom树的构建,我们可以从webkit的内核来看分为下面几步:
来自: 《WebKit技术内幕》.朱永盛
1. 当用户输入网页URL的时候,WebKit调用其资源加载器加载该URL对应的网页。
2. 加载器依赖网络模块建立连接,发送请求并接收答复。
3. WebKit接收到各种网页或者资源的数据,其中某些资源可能是同步或异步获取的。
4. 网页被交给HTML解释器转变成一系列的词语(Token)。
5.解释器根据词语构建节点(Node), 形成DOM树
6.如果节点是JavaScript代码的话,调用JavaScript引攀解释并执行。
7. JavaScript代码可能会修改DOM树的结构
8. 如果节点需要依赖其他资源,例如图片、CSS、视频等,调用资源加载器来加载它们,
但是它们是异步的,不会阻碍当前DOM树的继续创建;
如果是 JavaScript资源URL(没有标记异步方式),
则需要停止当前DOM树的创建直到JavaScript的资源加载并被JavaScript引擎执行后才继续DOM树的创建
其中我们最重要的就是去证明这句话:
如果节点需要依赖其他资源,例如图片、CSS、视频等,调用资源加载器来加载它们, 但是它们是异步的,不会阻碍当前DOM树的继续创建; 如果是 JavaScript资源URL(没有标记异步方式), 则需要停止当前DOM树的创建直到JavaScript的资源加载并被JavaScript引擎执行后才继续DOM树的创建
结论先上:
HTML解释器在解释它后构建成一棵DOM树,这期间如果遇到JavaScript代码则交给JavaScript引擎去处理;
如果网页中包含CSS,则交给CSS解释器去解释, 遇到js,会阻塞dom的的解析,而png,css,video是异步,不会阻碍dom树的构建
2如何测试
至于上面的结论我们估计很多人都知道,但是很少人去为了这个测试,贫穷使我进步做了这个测试
我看了一些文章,很多在服务端做了一个资源的缓慢返回,来验证效果,也就是说我加载的资源慢一点返回,来看看dom树不是已经DOM树构建完了,说实话,要是这么麻烦,我也不测
所以我采用的方法比较笨,重复加载同类型大资源,这样请求回来的速度(时间)肯定变慢.这里有很重要的两个事件.DOMContent事件与DOM的onload”事件,如果我加载js资源变大,DOMContent事件触发的时间与上方的同步代码时间差比不请求js代码的时间差大很多,是不是就已经说明了js会对dom的构建进行阻塞呢,而如果没啥变化,但是DOM的onload”事件的时间变大了,说明了不影响dom解析,是异步资源,当然也会相对应的影响渲染的速度,这里我们重点测试:遇到js,会阻塞dom的的解析,而png,css,video是异步,不会阻碍dom树的构建
DOMContent事件:
DOM树构建完之后触发
DOM的onload”事件
DOM树建完并且网页所依赖
的资源都加载完之后发生,因为某些资源的加载并不会阻碍DOM树的创建,所以这两个事件多数时候不是同时发生的
3 测试开始
基于以下的代码进行测试
<div>哈哈哈哈哈哈</div>
<div id="hello">哈哈哈哈哈哈</div>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
我刷新了很多次,dom的构建大概在0-1毫秒,而资源加载完大概0-3秒
3.1 js对dom的解析 -- 阻碍
<div>哈哈哈哈哈哈</div>
<div id="hello">哈哈哈哈哈哈</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
我刷新了很多次,dom的构建大概在2-3毫秒,而资源加载完大概4-5秒
假设我将js放到两个div之间
<div>哈哈哈哈哈哈</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<div id="hello">哈哈哈哈哈哈</div>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
从上面可以看出第二个哈哈哈哈,要等js加载完了之后才能显示,而上面那个例子一开始就能显示,后面进行了修改,现在我将script放到body之下, 时间差和上面是一致的,但是不会阻碍上面的dom加载,毕竟是从上到下执行的,如果将script标签放到最后,时差会很大,因为是先执行上面的script标签的里面的js的.所以是比较推荐将外部引入的script以及script标签放到body标签内的
3.2 css对dom的解析 -- 不阻碍
<div>哈哈哈哈哈哈</div>
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<div id="hello">哈哈哈哈哈哈</div>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
我刷新了很多次,dom的构建大概在0-1毫秒,而资源加载完大概36毫秒,所以说css对dom的解析不阻碍,但是你可以看看link对dom的渲染是阻塞的,因为如果不阻塞,哈哈哈哈应该是立马出来的哦~
3.3 图片对dom的解析 -- 不阻碍
这里找不到大图片,用gif图比较明显哈
<div>哈哈哈哈哈哈</div>
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fww2.
sinaimg.cn%2Fmw690%2F92e8647ajw1ey8fylzi44g20b405k1kx.gif&refer=http%3A%2F%2Fwww.sina.
com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1622278178&t=
7d9d25925a71ac7e0d45138e517ff690">
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fww2.
sinaimg.cn%2Fmw690%2F92e8647ajw1ey8fylzi44g20b405k1kx.gif&refer=http%3A%2F%2Fwww.sina.
com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1622278178&t=
7d9d25925a71ac7e0d45138e517ff690">
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fww2.
sinaimg.cn%2Fmw690%2F92e8647ajw1ey8fylzi44g20b405k1kx.gif&refer=http%3A%2F%2Fwww.sina.
com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1622278178&t=
7d9d25925a71ac7e0d45138e517ff690">
<div id="hello">哈哈哈哈哈哈</div>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
我刷新了很多次,dom的构建大概在0-1毫秒,而资源加载完大概28-31毫秒,所以说css对dom的解析不阻碍
3.4 defer和async异步加载js
上面有提到图片和css是异步加载,所以不会影响dom的解析,那么我们也可以将加载的script变成异步呀 使用async
<div>哈哈哈哈哈哈</div>
<div id="hello">哈哈哈哈哈哈</div>
<script async type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script async type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script async type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
<script>
const timer = new Date().getTime()
window.onload = () => {
console.log(new Date().getTime() - timer,'onload-全部的资源加载完');
}
document.getElementById('hello').innerHTML = '开始测试'
document.addEventListener('DOMContentLoaded',function(){
console.log(new Date().getTime() - timer, 'DOMContentLoaded-dom树形成');
});
</script>
相比上面的,dom解析完的时间差变小了,说明js异步加载了
使用defer
相比上面的,dom解析完的时间差很大,说明是在加载完了之后再执行DOMContentLoaded
按照之前的设想,应该使用defer与async表现一样,但是这个取决于本身对defer和DOMContentLoaded触发前后的一个规定(JS高级程序设计第4版):
async给脚本添加 async 属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到
该异步脚本下载和执行后再加载其他脚本
如果存在多个有defer属性的脚本,那么它们是按照加载顺序执行脚本的;而对于async,它的加载和执行是紧紧挨着的
defer是在DOMContentLoaded之前执行,async 异步脚本保证会在页面的 load事件前执行,但可能会在 DOMContentLoaded之前或之后
这里的script代码的位置,为了保持一致性,放在了最后,可能时间上有点不准,但是主要是看时间差哈