JS 和 CSS 对 DOM 树构建的影响

分析 JavaScript 和 CSS 如何阻塞 DOM 解析和渲染过程

问题

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

解答

基本原理

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

内联 JavaScript 脚本

当 HTML 中包含内联脚本时,解析器会暂停 DOM 构建:

<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 文件

引入外部 JS 文件会增加下载时间:

// 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 解析
  • defer:并行加载,HTML 解析完成后按顺序执行,在 DOMContentLoaded 事件前执行

CSS 样式的影响

当 JavaScript 需要操作 CSSOM 时,必须等待 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,渲染引擎在遇到 <script> 标签时,会先执行 CSS 文件下载和解析,再执行 JavaScript 脚本。这意味着样式文件会阻塞 JavaScript 的执行。

关键点

  • JavaScript 会阻塞 DOM 解析,执行脚本时 HTML 解析器暂停工作
  • CSS 不阻塞 DOM 解析,但会阻塞 JavaScript 执行(因为 JS 可能操作 CSSOM)
  • 外部 JavaScript 文件的下载过程会阻塞 DOM 解析
  • 使用 asyncdefer 可以实现脚本异步加载,避免阻塞
  • 浏览器的预解析线程会提前下载 JavaScript 和 CSS 文件进行优化