灏天阁

浏览器渲染HTML全过程详解,包括preload,prefetch,defer,async用法

· Yin灏

HTML 解析过程

  1. 解析 HTML 文档,构建 DOM(Document Object Model)树
  2. 假如遇到 link 标签(没有其他特殊的属性),异步下载对应的样式文件,下载完毕开始构建 CSSOM 树,此过程并不会阻塞 DOM 树的构建(但会影响渲染树,即所谓的阻塞渲染)
  3. 假如遇到 script 标签(没有其他特殊的属性),阻塞 DOM 的构建,并在下载完成后直接执行 JS 代码
  4. DOM 树构建完成,DOMContentLoaded 事件触发
  5. 结合 DOM 树和 CSSOM 树,形成渲染树
  6. 计算布局和绘制元素,呈现页面,window load 事件触发

验证解析过程

所需工具

  1. chrome 浏览器
  2. 一个 js 和 css 文件 cdn 链接
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>
      document.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
      });
      window.addEventListener("load", () => {
        console.log("load");
      });
    </script>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
    />
  </head>
  <body>
    <div class="test">This is the content of the test div</div>
  </body>
</html>

我们在进入页面时,监听了页面的 DOMContentLoad 和 load 事件,同时引入一个 css 文件,接着,我们打开该网页,在 chrome 浏览器中将网速调到 1kb/s。清空缓存刷新网页。

1680074781152.png

1680074771174.jpg

可以看到当浏览器在下载 css 文件的过程中,DOM 树已经构建完成了,因此 CSS 文件的下载并不会阻塞 DOM 树的构建。

场景 2:script 标签会阻塞 DOM 树的构建

和场景 1 同样的代码,但将引入的 css 文件换成 js 文件,如

<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>

同样的,调低网速,清空缓存刷新网页。

1680075244468.jpg

1680075257105.jpg

可以看到,在 js 下载过程中,DOMContentLoaded 事件一直没被触发。此时取消网络限制,让浏览器下载完 JS 文件,可以看到控制台打印信息

image.png

此时可以看到 DOMContentLoaded 事件和 load 事件都已经触发了,证明了 script 标签的下载和执行会阻塞 DOM 树的构建。

css 文件下载同样会影响网页的呈现

在场景 1 的例子截图中,我们可以看到在下载 css 文件的过程中,虽然 DOMContetLoaded 事件触发了,但一直没有触发 load 事件,且页面是空白的。这是因为最终页面的呈现是由渲染树决定的,而渲染树又由 DOM 树和 CSSOM 树共同决定,因此在 CSSOM 树构建完成之前,页面就可能出现留白的现象。

为何推荐将 css 样式放在 head 中,而将 js 代码写在 body 之前

浏览器在渲染页面过程中,会尽可能先呈现已解析内容,由上面可知,CSSOM 树和 DOM 树的构建是异步的,因此,将样式代码放在 head 标签中,可以让浏览器更早地构建 CSSOM 树和 DOM 树,尽早的呈现完整的页面。

但由于 JS 代码会阻塞 DOM 树的构建,且会被立刻执行,将其放在后面才能避免操作不到页面最终需要的东西,如:

image.png

为何说 DOMContentLoaded 事件执行时,还可能存在一些 js 和 css 未被加载

  1. 存在 css 未被加载情况从场景 1 很容易看出
  2. 本文前面说 script 标签会阻塞 DOM 树的构建,按道理只有页面上的所有脚本都执行完之后,DOMContentLoaded 事件才可能完成,但这只限于未给 script 标签加其他属性的情况,假如给 script 加上 async 属性,情况又有不同。

preload,prefetch,defer,async

defer,async

这两个属性都是 script 标签上的属性,其中

  1. async 表示脚本会被并行请求,并尽快解析和执行。因此假如 scirpt 的下载速度很慢,且有 async 属性,则在 DOMContentLoaded 时候,就可能存在还未执行的 js 文件。如

image.png

image.png

image.png

可见,此时 DOMCotentLoaded 已经触发了,但 async 的 script 资源尚未下载完。

  1. defer 表示通知浏览器该脚本将在文档完成解析后,触发  DOMContentLoaded  事件前执行。如

image.png

image.png

可见资源还未下载完时,会阻塞 DOMContentLoaded 事件

preload,prefetch 同样会让资源下载变成异步

preload 和 prefetch 都是优化浏览器加载资源的手段,它们都是 link 标签上的属性,与之相关的还有一个 as 属性,可以用来表示连接的文档内容,as 属性仅在 rel 属性设置了 preload 或 prefetch 有效。

  1. preload 指示某资源为重要资源,指示浏览器在加载当前页面时必须立即下载资源,它只会指示该资源下载和缓存的优先级而不会加载该 js 文件,如
<link rel="preload" href="style.css" as="style">
<!-- 它的`as`属性指定了资源类型,以便浏览器可以优化加载。 -->

在使用的时候,还是需要再引入一次

<link rel="stylesheet" href="style.css">

让我们用 animate.css 来验证这点,看下面例子

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <script>
      document.addEventListener("DOMContentLoaded", () => {
        console.log("DOMContentLoaded");
      });
      window.addEventListener("load", () => {
        console.log("load");
      });
    </script>
    <link
      rel="preload"
      href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
      as="style"
    />
  </head>
  <body>
    <h1 class="animate__animated animate__bounce">An animated element</h1>
  </body>
</html>

假设 preload 只是让浏览器提前下载该文件而不加载,h1 元素在页面加载完的时候就不会抖动。让我们看结果:

image.png

确实浏览器下载了该文件,且 h1 元素没有抖动。

接下来我们在使用普通的 link 引入 animate css,如

<link
  rel="preload"
  href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
  as="style"
/>
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css"
/>

此时清空缓存再次刷新页面。 可以看到浏览器下载了 animate.css 并且 h1 元素抖动。

  1. prefetch 用于指示浏览器在将来可能需要使用的资源。如
<link rel="prefetch" href="image.png">

这告诉浏览器,在当前页面加载完成后,可以预取image.png文件,因为它可能在用户进行后续导航时需要使用。这可以减少页面之间的加载时间。

- Book Lists -