CSS、JS 阻塞 DOM 解析渲染

为什么 <script> 最好放底部,<style> 最好放头部?如果头部同时有 <script> 与 <link>,最好将 <script> 放在 <link> 上面吗?

1. CSS

CSS 不会阻塞 DOM 解析

浏览器解析 DOM 生成 DOM Tree,结合 CSS 生成的 CSS Tree,最终组成 Render Tree 再渲染页面。在这个过程中,CSS 完全无法影响 DOM Tree,因此无需阻塞 DOM 解析。

CSS 阻塞页面渲染

DOM Tree 和 CSS Tree 会组合成 Render Tree。如果 CSS 不阻塞渲染,那么 CSS 下载之前浏览器就会渲染出"原始"页面,下载完成后再变成"完整"页面——用户体验差且渲染成本高。

基于性能与用户体验的考虑,浏览器会尽量减少渲染次数,所以 CSS 阻塞页面渲染。

特殊例子

<header>
  <link rel="stylesheet" href="/css/sleep3000-common.css">
  <script src="/js/logDiv.js"></script>
</header>

浏览器会转圈圈三秒,期间不会打印任何东西,之后呈现浅蓝色 div,再打印 null。

这是 CSS 阻止了 DOM 解析吗?不是,是 <script> 阻塞。

原因:脚本如果获取元素的样式(依赖 CSS),浏览器需要先计算样式再执行 JS。浏览器无法预知脚本内容,为避免样式错位,只好等前面所有样式下载完再执行 JS。

2. JS

<script> 阻塞 DOM 解析

浏览器不知道脚本的内容,如果先解析下面的 DOM,万一脚本删除/重写了后面的节点,浏览器就白干活了。浏览器无法预估内容,干脆全部停住,等脚本执行完再继续。

优化

  • 体积大且不阻塞首屏:加 defer 或 async:
    • defer:等 DOM 解析完再执行,按顺序执行(适合依赖 DOM 的脚本)。
    • async:下载完立刻执行,不保证顺序(适合独立脚本,如统计 SDK)。
  • 执行时间长:拆分代码,不用立即执行的可以 setTimeout。
  • 现代浏览器会预读后面的 DOM,提前下载 link、script、img 等资源,不会等到解析到那里再下载。

浏览器遇到 <script> 时会触发页面渲染

这是解释"JS 执行会等待 CSS 下载"的根因:浏览器不知道脚本内容,碰到脚本就渲染一次页面,确保脚本能拿到最新的 DOM 信息(即使脚本并不需要)。

总结:

  • CSS:阻塞渲染,不阻塞 DOM 解析;执行 JS 时若依赖 CSS 会被同步阻塞。
  • JS:阻塞 DOM 解析(无 defer/async);执行前后都可能触发一次渲染。

来源整理自:我的有道云笔记