🧠 一、浏览器加载网页的流程
加载一个网页时,浏览器大致会经过这些步骤:
-
解析 HTML → 构建 DOM 树
-
解析 CSS → 构建 CSSOM(CSS Object Model)
-
合并 DOM + CSSOM → 构建 Render Tree(渲染树)
-
布局(Layout) → 计算元素位置和大小
-
绘制(Paint) → 把内容画在屏幕上
现在我们来看 JavaScript 和 CSS 分别在这个流程里是怎么影响的。
🎨 二、CSS 的行为
✅ CSS 会阻塞页面渲染(但不会阻塞 HTML 解析)
当浏览器遇到 <link rel="stylesheet" href="style.css">
这样的标签时,它会异步请求这个 CSS 文件。
虽然 HTML 解析不会停下来继续进行,也就是 DOM 树会照常构建,但浏览器不会开始“绘制”页面上的内容(也就是不会显示出来),直到这个 CSS 文件加载并解析完毕。
为什么 CSS 阻塞渲染?
浏览器在渲染页面时需要知道样式信息,如果没有加载完 CSS,浏览器就无法确定页面元素的外观,比如它们的颜色、大小和位置。这样,页面可能会先以没有样式的默认状态显示一会儿,或者显示一些错误的布局,给用户造成“闪烁”或者“布局跳变”的不良体验。因此,浏览器会等 CSS 加载并解析完成后,才会开始最终渲染页面,确保样式和布局的准确性。
💻 三、JavaScript 的行为
❌ JS 默认会阻塞 HTML 解析(尤其是同步加载的)
当浏览器遇到 <script src="app.js"></script>
这样的脚本:
- HTML 解析暂停(DOM 构建中止)
- 浏览器去下载 JS 文件(同步行为)
- 下载完成后,立即执行 JS
- 执行完成后,HTML 解析继续
为什么 JS 会阻塞解析?
JavaScript
可能会动态地修改页面内容,比如通过 document.write()
插入新的 HTML,或者修改 DOM 结构。如果浏览器在解析 HTML 时遇到这些 JavaScript,它无法保证页面结构的完整性和一致性。因此,浏览器必须等 JS 执行完毕后,才能确保 HTML 解析的安全进行,避免出现不完整或混乱的页面结构。
🚀 四、解决方案:异步加载 JS 的两种方式
1. defer
<script src="main.js" defer></script>
-
✅ 异步下载 JS
-
✅ HTML 解析不会被阻塞
-
✅ 在 DOM 构建完成之后执行
-
✅ 保证多个脚本按顺序执行(重要!)
📌 推荐使用场景:几乎所有非内联的 JS 都可以用 defer
2. async
<script src="main.js" async></script>
- ✅ 异步下载 JS
- ⚠️ 下载完成就立刻执行(可能早于 HTML 完成)
- ❌ 多个 async 脚本之间 执行顺序不确定
📌 适合场景:统计、广告、第三方库(不依赖 DOM,不互相依赖)
🧩 五、实际效果小结
文件类型 | 是否阻塞 HTML 解析 | 是否阻塞页面渲染 |
---|---|---|
CSS | ❌ 不阻塞 HTML 解析 | ✅ 阻塞页面渲染 |
JS(默认) | ✅ 阻塞 HTML 解析 | ✅ 阻塞页面渲染 |
JS(defer) | ❌ 不阻塞 HTML 解析 | ✅ 等 DOM 完成后执行 |
JS(async) | ❌ 不阻塞 HTML 解析 | ❌ 立即执行,可能影响渲染时机 |
🔧 实际建议
- CSS 永远放在
<head>
,确保优先加载,避免渲染阻塞。 - JS 推荐使用
defer
放在<head>
,或放在<body>
底部。 - 不要阻塞主线程,能异步就异步。
- 重要脚本尽量合并和压缩,减少请求数量和大小。