JS 与 CSS 对 DOM 构建的影响

JavaScript 和 CSS 如何影响 DOM 树的解析与渲染

问题

JavaScript 和 CSS 是如何影响 DOM 树构建的?

解答

基本原理

CSS 不会阻塞 DOM 的解析,但会影响 JavaScript 的运行。JavaScript 会阻止 DOM 树的解析,而 CSS(CSSOM)会影响渲染树的生成。

内联 JavaScript 脚本

<html>
  <body>
    <div>1</div>
    <script>
      let div1 = document.getElementsByTagName('div')[0]
      div1.innerText = 'time.geekbang'
    </script>
    <div>test</div>
  </body>
</html>

当解析到 <script> 标签时,HTML 解析器暂停工作,JavaScript 引擎介入并执行脚本。脚本执行完成后,HTML 解析器恢复解析,继续处理后续内容。

外部 JavaScript 文件

// foo.js
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'
<html>
  <body>
    <div>1</div>
    <script type="text/javascript" src='foo.js'></script>
    <div>test</div>
  </body>
</html>

执行流程与内联脚本相同,但需要先下载 JavaScript 文件。文件下载过程会阻塞 DOM 解析,下载时间受网络环境和文件大小影响。

浏览器优化: Chrome 会开启预解析线程,提前分析 HTML 中的 JavaScript、CSS 文件并下载。

优化策略:

使用 asyncdefer 属性实现异步加载:

<script async type="text/javascript" src='foo.js'></script>
<script defer type="text/javascript" src='foo.js'></script>
  • async:脚本并行加载,加载完成后立即执行,执行时机不确定,可能阻塞 HTML 解析,在 load 事件前执行
  • defer:脚本并行加载,等待 HTML 解析完成后按加载顺序执行,在 DOMContentLoaded 事件前执行

CSS 样式的影响

/* theme.css */
div {color: blue}
<html>
<head>
    <link rel="stylesheet" href='theme.css'>
</head>
<body>
  <div>1</div>
  <script>
      let div1 = document.getElementsByTagName('div')[0]
      div1.innerText = 'time.geekbang' // 需要 DOM
      div1.style.color = 'red' // 需要 CSSOM
  </script>
  <div>test</div>
</body>
</html>

当 JavaScript 代码操作 CSSOM(如 div1.style.color)时,必须先解析所有 CSS 样式。如果引用了外部 CSS 文件,需要等待文件下载并解析生成 CSSOM 后,才能执行 JavaScript。

由于渲染引擎无法预知 JavaScript 是否会操作 CSSOM,遇到脚本时都会先执行 CSS 文件的下载和解析,因此样式文件会阻塞 JavaScript 的执行。

关键点

  • CSS 不阻塞 DOM 解析,但会阻塞 JavaScript 执行
  • JavaScript 会阻塞 DOM 树的解析和构建
  • 外部 JavaScript 文件的下载过程会阻塞 DOM 解析
  • 使用 asyncdefer 可实现脚本异步加载,避免阻塞
  • CSS 文件会阻塞 JavaScript 执行,因为 JavaScript 可能操作 CSSOM