DomcontentLoaded事件何时触发
HTML被完全加载及解析时,DomcontentLoaded事件会被触发
MDN的解释是当HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发。那HTML被完全加载及解析又指的是什么呢,如果我们搞清楚这个概念就能清晰的知道DOMContentLoaded的触发时机。
我们先来看一下浏览器的渲染过程:
浏览器获取到html文件后就开始解析生成Dom对象,这里当Dom对象生成之后就表示HTML被加载及解析完成了。此外如果有css会根据css生成cssom对象,然后再由cssom和dom合并生成渲染树。有了渲染树便根据节点和样式计算出它们在浏览器中的大小和位置,这就是布局阶段,之后再把节点绘制到屏幕上,这样就完成了第一次渲染。
上面我们已经说了当DOM对象生成之后就表示HTML被加载及解析了,这个时候会触发DomcontentLoaded事件,如果html文档中没有script脚本时这个构建dom的流程很简单,而当包含script和css时情况就变得复杂起来,下面我们来看一下script和css是怎么影响Dom构建的。
script如何影响dom构建
内联脚本 & 同步脚本
<html>
<head>
// 内联脚本
<script>
console.log("Hello");
</script>
</head>
<body>
// 同步脚本
<script src="http://www.example.com/example.js"></script>
<script src="http://www.example.com/example2.js"></script>
</body>
</html>
渲染进程在解析html文档时如果遇到内联脚本会直接执行,遇到外部同步脚本会先加载再执行,在此期间会停止dom构建,无论遇到内联或同步的外部脚本都秉承着由上至下串行执行的原则。所以当文档中包含脚本时,脚本会阻塞dom的构建。
defer 异步脚本
<html>
<head>
<script src="http://www.example.com/example.js" defer></script>
<script src="http://www.example.com/example2.js" defer></script>
</head>
<body>
</body>
</html>
渲染进程在遇到defer脚本时,会使用其它进程或线程异步加载脚本,渲染进程继续解析dom,当dom解析完成后再执行defer脚本,如果有多个defer脚本会按照它们在文档中的位置按顺序执行。所以defer脚本不影响domcontentloaded事件触发,它会在该事件触发后再执行。
async 异步脚本
<html>
<head>
<script src="http://www.example.com/example.js" async></script>
<script src="http://www.example.com/example2.js" async></script>
</head>
<body>
</body>
</html>
渲染进程在遇到async脚本时,会使用其它进程或线程异步加载脚本,当加载成功后直接执行,如果有多个async脚本则执行顺序不确定,在执行阶段会暂停dom解析。async有两种情况,如果加载成功时dom已解析完成,则不影响domcontentloaded触发时机,如果加载成功时dom还未解析完成,则会影响domcontentloaded的触发时机。
css如何影响dom构建
渲染进程在遇到link标签时,会另起线程异步加载构建cssom,所以css不会直接影响到dom构建。但考虑到脚本在执行时可能会读取或修改cssom,所以脚本在执行前会需要等待前面所有的样式表加载并构建成cssom后才会执行,因此link也会影响到dom构建。
- link后无script:不影响dom构建
- link后有同步script:影响dom构建
- link后有defer script:不影响dom构建
- link后有async script:取决于脚本的执行时机
总结
| 是否影响dom构建 | |
|---|---|
| 同步script | 是 |
| defer script | 否 |
| async script | 可能影响,加载完成时若dom未构建完成,则影响,若dom已构建完成则不影响 |
| link | 是否影响取决于其后是否有script及script的类型 |
我们可以试着从浏览器的实现角度去理解这个问题,渲染进程在解析构建dom的过程中是同步执行的,如果它遇到script是同步去加载执行的,肯定会暂停dom的构建,而如果它异步去加载,就不会影响dom的构建,所以我们可以从defer、async、style的加载执行是异步还是同步,以及它的执行时机来分析它是否会影响dom构建。defer、async的加载是异步的,执行都是同步的,defer是等到dom构建完成后再执行,async是加载完就执行。
补充defer、async的使用场景
defer
- 非关键性脚本,不影响页面首屏渲染或用户初始视图的脚本,可以使用defer优化加载
- 需要等dom解析完才执行,即需要访问或修改dom节点
async
- async脚本执行没有顺序,适用于比较独立的脚本,脚本之间没有依赖关系,并且不依赖于特定的dom结构。
defer其实很像把script放在body底部的效果,区别在于它可以提前进行下载,不过现在的构建工具一般会使用link preload来实现脚本的预下载,所以效果基本一致,但是defer不会影响domcontentloaded事件的触发。