做了这么多年前端,你真的了解html中资源加载执行时序吗
做了好几年前端了,最近想进个大厂,参加了阿里、百度、今日头条的面试,被彻底的打击了,感觉自己的基础不是很牢,所以萌发了重学前端的想法,向习以为常的知识/技能,多问一个为什么,多了解一下原理,或者源码
今天我们来聊聊html中资源加载和执行的顺序问题。
做了几年前端了,可是当浏览器获取到html文件后怎么加载执行文件,这些问题你能回答吗
按照什么顺序加载文件?
每次能加载多少个文件?
css没加载完会执行js吗?
async和defer的区别是什么?
DOMContentLoaded事件和onload事件,什么时间触发?
为了搞清楚这些问题,我们来做下试验,我用的是chrome浏览器。
我们先构造一个html文件,在head里面交替加载10个css文件和10个js文件,这几个文件地址是我用本地php服务创建的,都延迟3秒返回数据,以便我们清楚观察效果
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="http://test.com/test/css1"> <script src="http://test.com/test/js1"></script> <link rel="stylesheet" href="http://test.com/test/css2"> <script src="http://test.com/test/js2"></script> <link rel="stylesheet" href="http://test.com/test/css3"> <script src="http://test.com/test/js3"></script> <link rel="stylesheet" href="http://test.com/test/css4"> <script src="http://test.com/test/js4"></script> <link rel="stylesheet" href="http://test.com/test/css5"> <script src="http://test.com/test/js5"></script> <link rel="stylesheet" href="http://test.com/test/css6"> <script src="http://test.com/test/js6"></script> <link rel="stylesheet" href="http://test.com/test/css7"> <script src="http://test.com/test/js7"></script> <link rel="stylesheet" href="http://test.com/test/css8"> <script src="http://test.com/test/js8"></script> <link rel="stylesheet" href="http://test.com/test/css9"> <script src="http://test.com/test/js9"></script> <link rel="stylesheet" href="http://test.com/test/css10"> <script src="http://test.com/test/js10"></script> </head> <body> </body> </html>
css和js的地址我是使用php框架构造的,目的是为了使用php的sleep延迟返回资源,好让我们更清楚的看到时序,你可以通过node或其他后台自行构造
//js文件,等待3s后返回 public function actionJs1(){ sleep(3); return "console.log('js1111111')"; } //css文件,等待3s返回 public function actionCss1(){ sleep(3); }
我们通过chrome来看下加载时序,可以看到,浏览器首先下载html文件,下载之后紧接着按照文档顺序请求css1、js1、css2、js2、css3、js3,可以看到浏览器一次可以加载6个文件,加载完成之后再加载6个,直到全部加载完成。
而加载顺序实际反复测试几次,均为【css1、js1、css2、js2、css3、js3】、【css4、css5、css6、css7、css8、css9】、【css10、js4、js5、js6、js7、js8】、【js9、js10】,可以看出,有两个特点:整体来说是按照文档在html中出现顺序进行加载的,但是会部分优先加载css文件
为了证实上面的猜想,有对html代码进行了改造,10个css全放前面,10个js全放后面
<link rel="stylesheet" href="http://test.com/test/css1"> ...... <link rel="stylesheet" href="http://test.com/test/css10"> <script src="http://test.com/test/js1"></script> ...... <script src="http://test.com/test/js10"></script>
果然,浏览器优先把前面10个css先加载完毕,然后才加载js
而如果我们把10个js放到10个css文件前面呢
<script src="http://test.com/test/js1"></script> ...... <script src="http://test.com/test/js10"></script> <link rel="stylesheet" href="http://test.com/test/css1"> ...... <link rel="stylesheet" href="http://test.com/test/css10">
可以看到,浏览器先加载了6个js,然后又优先把10个css加载完,之后才加载js
接下来在body中再添加10个图片,看看css、js、图片,这三种资源的加载顺序,不用测试我们应该也能想到,图片应该在css和js之后,实验验证一下。
构造测试html,每个资源设置6个,顺序如下
<link rel="stylesheet" href="http://test.com/test/css1"> ...... <link rel="stylesheet" href="http://test.com/test/css6"> <img src="http://test.com/test/img1"/> ...... <img src="http://test.com/test/img6"/> <script src="http://test.com/test/js1"></script> ...... <script src="http://test.com/test/js6"></script>
chrome文件加载时序为,先加载6个css、再加载6个js,最后加载6个图片
浏览器每次只能加载6个文件,那还能不能再多加载一些呢,假如加载的资源域名不同,会发生什么?
//前六个css域名为test.com <link rel="stylesheet" href="http://test.com/test/css1"> ...... <link rel="stylesheet" href="http://test.com/test/css6"> //后六个css域名为m.test.com <link rel="stylesheet" href="http://m.test.com/test/css1"> ...... <link rel="stylesheet" href="http://m.test.com/test/css6">
可以看到,浏览器一次性加载了12个文件
1.浏览器加载文件,整体顺序是按照文件在html中出现的顺序进行加载
2.但是会优先加载css、然后加载js、最后加载图片
3.同一个域名的资源,谷歌浏览器每次加载6个,不同域名的资源可以并行加载
看到这里,我们应该会性能优化有了一些想法了吧,比如使用不同域名可以提高整体加载速度
js会在加载成功立即执行吗,还是会受到别的因素影响呢?我们先来测试一下吧,假如css延迟3s返回,js取消延迟立即返回,看看会发生什么
//css延迟3s返回数据 <link rel="stylesheet" href="http://test.com/test/css1"> //js立即返回,console.log('js1') <script src="http://test.com/test/js1"></script>
刷新页面可以看到尽管js很快就加载完成了,但是并没有立即执行,等了3s左右才执行,也就是在css加载完成后才执行,即css的加载会阻塞js的执行,那是不是因为css在js前面的原因呢?
我们调换一下css和js的顺序,则可以看到js立即打印,并不会等待css的加载
//js立即返回,console.log('js1') <script src="http://test.com/test/js1"></script> //css延迟3s返回数据 <link rel="stylesheet" href="http://test.com/test/css1">
如果css在js前面,则css的加载会阻塞js的执行,即css加载完毕后,执行js
如果css在js后面,则不会阻塞js的执行
接下来我们看看两个js的加载执行顺序,先从最普通的开始
//js1延迟5s返回数据,console.log('js1') <script src="http://test.com/test/js1"></script> //js2延迟3s返回数据,console.log('js2') <script src="http://test.com/test/js2"></script> <script> console.log('js3') </script>
等待5s后,控制台先后打印 js1 js2 js3,虽然js2提前加载完成,但是仍然要等待前面的js1加载执行完毕才能执行
js异步加载执行有两种方式async和defer,接下来我们给js添加async属性看看
//js1延迟5s返回数据,console.log('js1') <script src="http://test.com/test/js1" async></script> //js2延迟3s返回数据,console.log('js2') <script src="http://test.com/test/js2" async></script> <script> console.log('js3') </script>
控制台立即打印顺序为
js3 js2 js1
可见添加async属性js不会阻塞其后面js的执行,谁先加载完成谁先执行,接下来添加defer属性。
//js1延迟5s返回数据,console.log('js1') <script src="http://test.com/test/js1" defer></script> //js2延迟3s返回数据,console.log('js2') <script src="http://test.com/test/js2" defer></script> <script> console.log('js3') </script>
控制台打印顺序如下,可以看出,添加defer属性的js,不会阻塞后面的js执行,但是多个添加defer的js,仍然按照既有顺序执行。
js3 js1 js2
下面是一张经典js加载执行时机对比图,
浏览器遇到没有添加异步属性的js,会立即加载并执行,也就是说会阻塞html的解析
浏览器遇到添加async属性的js会立即加载(当然了,如谷歌浏览器,就算没有添加async,它也会提前识别html中的js文件进行加载,加载时机如第一部分讨论),并在js加载完毕之后立即执行;多个async属性的js谁先加载完成谁先执行
浏览器遇到添加defer属性的js会立即加载,但是不会立即执行,而是会在html解析完成之后,DOMContentLoaded触发之前执行;多个defer属性的js会按照文档顺序执行
上面提到添加defer属性的js会在文档解析之后,DOMContentLoaded触发之前执行,也就是说,在添加defer属性的js中我们是可以获取到dom的
//js1延迟5s返回数据,console.log(document.getElementById('test')) <script src="http://test.com/test/js1" defer></script> <script> console.log(document.getElementById('test')) </script> <div id="test"></div>
内联js,在id=“test”的dom之前,所以输出null,而defer属性的js打印出了dom
null <div id="test"></div>
即在defer属性的js中,可以安全的操作dom
如果css在js前面,则css的加载会阻塞js的执行,即css加载完毕后,执行js
如果css在js后面,则不会阻塞js的执行
非异步js按照顺序进行执行
多个async属性的js,谁先加载完成谁执行
defer属性的js在文档解析之后,DOMContentLoaded触发之前执行,多个defer属性的js按文档顺序执行
网上查看一些资料,一般说法是这样的
1、当 onload 事件触发时,页面上所有的DOM,样式表,脚本,图片,flash都已经加载完成了。
2、当 DOMContentLoaded事件触发时,仅当DOM加载完成,不包括样式表,图片,flash。
但是通过测试发现并非如此,假设css延迟3s返回,图片延迟5s返回,我们看看打印
//css延迟3s返回数据 <link rel="stylesheet" href="http://test.com/test/css1"> <script> document.addEventListener('DOMContentLoaded',function () { console.log('DOMContentLoaded') },false) window.onload=function () { console.log("onload") } </script> //延迟5s返回数据 <img src="http://3w.com/test/img1"/>
可以看出3s之后才打印DOMContentLoaded,5s之后打印onload,也就是说DOMContentLoaded是需要等待css加载完成的
我们刚才前面说了,defer属性的js要在DOMContentLoaded之前执行,那么假如defer属性延迟3s返回呢,我们看看效果
<script> document.addEventListener('DOMContentLoaded',function () { console.log('DOMContentLoaded') },false) window.onload=function () { console.log("onload") } </script> //js1延迟3s返回数据 <script src="http://test.com/test/js1" defer></script> //延迟5s返回数据 <img src="http://3w.com/test/img1"/>
同样的也在3s之后打印DOMContentLoaded,5s之后打印onload,可以说网上的结论和实际是不相符的
当 页面上所有的DOM,样式表,脚本,图片,flash都加载完成之后触发onload。
当DOM加载解析完成、css加载完成、内联js执行完成、defer属性的js完成才会触发DOMContentLoaded,图片和async属性的js,不会阻止发DOMContentLoaded的加载。
同样的这些结论也给我们做前端优化提供一些思路,比如给js添加async属性、减少js及css大小、使用懒加载减少图片请求,使其尽快进入onload事件等等
有兴趣的同学欢迎关注公众号,让我们一起重学前端,夯实基础。
点赞(2)