背景介绍
之前写过一篇关于不同 DOM 操作结果不同的文章,那篇文章只是简单的介绍了一下 HTML 及外部资源与 JS 脚本执行的一个时机,其实这个还可以再拓展一下,比如 JS 和 DOMContentLoaded 的关系,这也是本篇文章将要介绍的东西
HTML 与 DOM
我们请求网页时,首先是请求的 HTML 然后才是外部资源和 JS 脚本的下载,所以在顺序上是先构建的 DOM
当构建完 DOM 树的时候,DOMContentLoaded 事件就被触发了,可能有家人们想问了,那这个 DOMContentLoaded 一定很早就被触发了吧?那我在 Chrome 上的 NetWork 里看的不是这样的,这个很久才触发的喔,那是因为你可能已经是二次访问该页面,页面已经有了缓存,而 load 和 DOMContentLoaded 最大的区别就是 load 包含加载外部资源的时间,比如图片,CSS 样式表,如果它们都被缓存了,那么两个事件触发的时间差距自然不会有太大
外部资源不会影响 DOMContentLoaded 事件触发的结果,因为它们的下载是由专门的下载线程异步下载,但是脚本会(JavaScript,JS),看下 render tree 生成的过程
同步 JS
HTML 解析,遇到同步 JS 时,得让它先执行,怎么看的出来呢?下面有个小例子
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("出现文字");
});
for (let i = 0; i < 1000000000; i++) {}
console.log("end");
</script>
<p>究竟什么时候解析到我呢?</p>
</body>
</html>
<p>
元素有一段时间的延迟,才能到它展现自我,有的家人可能要问,你这个不对,你都把 <script>
塞在 <body>
里了,当然会阻塞,你试试放到尾部看看?其实像这段 JS 代码,你放在 HTML 文档的任意部分都是这样 end -> '出现文字' 的结果,把 for 循环结束的点设置的更长一点效果将更加明显,网页加载的小圈会一直转,你甚至有足够的时间打开你的控制台看它是怎么打印出来的,这其实也是白屏时间形成的一个原因
注意,这个同步 JS 其实有 下载 + 执行 两个部分,上面因为是在 HTML 中的代码,因此没有下载,可以后台开服务器,设置延时返回,比如 3s,也是会导致白屏 3s 以上然后再渲染 DOM 的内容,而且这个 JS 下载是在 DOMContentLoaded 前
<!DOCTYPE html>
<html>
<head>
...
<script src="http://localhost:8000/temp.js"></script>
</head>
<body>
<p>究竟什么时候解析到我呢?</p>
</body>
</html>
异步 JS
异步 JS 有下面几类
- ajax 请求
- worker 中的 JS
前面两种是因为,发送请求到返回这段过程中不会阻塞 HTML 的解析,而对于 worker 是因为这是一种后台任务,相当于另一个线程,不会阻塞我们的 GUI 渲染线程
总结
对于 <script>
需要下载 JS 的,会推迟 DOMContentLoaded 的触发,可以通过添加 defer 属性解决,比如
<script defer></script>
同时理解了HTML 的解析和 JS 的关系,其实可以帮助我们去优化白屏时间和进行正确 DOM 操作