页面访问时渲染过程中 HTML、JS 的关系

1,015 阅读2分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

背景介绍

之前写过一篇关于不同 DOM 操作结果不同的文章,那篇文章只是简单的介绍了一下 HTML 及外部资源与 JS 脚本执行的一个时机,其实这个还可以再拓展一下,比如 JS 和 DOMContentLoaded 的关系,这也是本篇文章将要介绍的东西

HTML 与 DOM

render 树

我们请求网页时,首先是请求的 HTML 然后才是外部资源和 JS 脚本的下载,所以在顺序上是先构建的 DOM

img1.png

当构建完 DOM 树的时候,DOMContentLoaded 事件就被触发了,可能有家人们想问了,那这个 DOMContentLoaded 一定很早就被触发了吧?那我在 Chrome 上的 NetWork 里看的不是这样的,这个很久才触发的喔,那是因为你可能已经是二次访问该页面,页面已经有了缓存,而 load 和 DOMContentLoaded 最大的区别就是 load 包含加载外部资源的时间,比如图片,CSS 样式表,如果它们都被缓存了,那么两个事件触发的时间差距自然不会有太大

外部资源不会影响 DOMContentLoaded 事件触发的结果,因为它们的下载是由专门的下载线程异步下载,但是脚本会(JavaScript,JS),看下 render tree 生成的过程

HTML渲染.jpg

同步 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>

t_3.jpg

异步 JS

异步 JS 有下面几类

  1. ajax 请求
  2. worker 中的 JS

前面两种是因为,发送请求到返回这段过程中不会阻塞 HTML 的解析,而对于 worker 是因为这是一种后台任务,相当于另一个线程,不会阻塞我们的 GUI 渲染线程

总结

对于 <script> 需要下载 JS 的,会推迟 DOMContentLoaded 的触发,可以通过添加 defer 属性解决,比如

<script defer></script>

同时理解了HTML 的解析和 JS 的关系,其实可以帮助我们去优化白屏时间和进行正确 DOM 操作

参考资料

浏览器的工作原理

浏览器渲染流程