一文详解如何避免JavaScript 代码阻塞页面渲染
作者:Smilezyl
阻塞渲染是指浏览器在解析 HTML 构建 DOM 树的过程中,遇到 <script> 标签时会暂停 DOM 解析,等待脚本下载并执行完毕后才继续解析,本文给大家介绍了如何避免JavaScript 代码阻塞页面渲染,需要的朋友可以参考下
核心答案
阻塞渲染是指浏览器在解析 HTML 构建 DOM 树的过程中,遇到 <script> 标签时会暂停 DOM 解析,等待脚本下载并执行完毕后才继续解析。这是因为 JavaScript 可能会修改 DOM 结构(如 document.write),浏览器必须确保 DOM 的正确性。
避免阻塞的核心方法:
- 使用
async属性:脚本异步下载,下载完立即执行 - 使用
defer属性:脚本异步下载,DOM 解析完成后按顺序执行 - 将脚本放在
</body>前 - 动态创建 script 标签
深入解析
浏览器渲染流程
HTML → DOM Tree
→ Render Tree → Layout → Paint
CSS → CSSOM
阻塞机制详解
1. JavaScript 阻塞 DOM 解析
HTML解析 → 遇到<script> → 暂停解析 → 下载JS → 执行JS → 继续解析
2. CSS 也会间接阻塞
- CSS 本身不阻塞 DOM 解析,但阻塞渲染
- 如果 JS 在 CSS 之后,JS 会等待 CSSOM 构建完成(因为 JS 可能访问样式)
async vs defer 的区别
| 特性 | async | defer |
|---|---|---|
| 下载 | 异步,不阻塞解析 | 异步,不阻塞解析 |
| 执行时机 | 下载完立即执行 | DOM 解析完成后执行 |
| 执行顺序 | 不保证顺序 | 保证顺序 |
| 适用场景 | 独立脚本(统计、广告) | 有依赖关系的脚本 |
常见误区
- 误区:async 和 defer 可以同时使用
实际:同时存在时,现代浏览器优先使用 async
- 误区:内联脚本可以使用 async/defer
实际:async/defer 只对外部脚本有效
- 误区:放在 body 底部就不会阻塞
实际:仍会阻塞,只是此时 DOM 已基本解析完成,影响较小
代码示例
<!-- 1. 阻塞渲染(默认行为) -->
<script src="app.js"></script>
<!-- 2. async:异步下载,下载完立即执行 -->
<script async src="analytics.js"></script>
<!-- 3. defer:异步下载,DOM 解析后按顺序执行 -->
<script defer src="vendor.js"></script>
<script defer src="app.js"></script> <!-- 保证在 vendor.js 之后执行 -->
<!-- 4. 动态加载脚本 -->
<script>
const script = document.createElement('script');
script.src = 'lazy-module.js';
script.async = false; // 保证顺序执行
document.body.appendChild(script);
</script>
<!-- 5. 模块脚本(默认 defer 行为) -->
<script type="module" src="app.mjs"></script>
<!-- 6. 预加载关键资源 -->
<link rel="preload" href="critical.js" rel="external nofollow" as="script">
现代优化方案
// 使用 Intersection Observer 懒加载脚本
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const script = document.createElement('script');
script.src = entry.target.dataset.src;
document.body.appendChild(script);
observer.unobserve(entry.target);
}
});
});
// 使用 requestIdleCallback 在空闲时加载
requestIdleCallback(() => {
const script = document.createElement('script');
script.src = 'non-critical.js';
document.body.appendChild(script);
});
面试技巧
可能的追问方向
"async 和 defer 的执行时机具体是什么?"
- async:下载完成后立即执行,可能在 DOMContentLoaded 之前或之后
- defer:在 DOMContentLoaded 事件之前执行
"CSS 会阻塞 JS 执行吗?"
- 会。如果
<script>在<link>之后,JS 会等待 CSSOM 构建完成
"如何检测和量化阻塞时间?"
- Performance API、Lighthouse、Chrome DevTools Performance 面板
"type="module" 的脚本有什么特点?"
- 默认 defer 行为、严格模式、独立作用域、支持 import/export
展示深度的回答技巧
- 提及浏览器的预解析器(Preload Scanner)会提前扫描并下载资源
- 讨论 Critical Rendering Path 优化策略
- 结合实际项目经验,如 Webpack 的代码分割、动态 import
一句话总结
JS 阻塞 DOM 解析是因为可能修改 DOM;用 defer 保顺序、async 求速度、动态加载最灵活。
以上就是一文详解如何避免JavaScript 代码阻塞页面渲染的详细内容,更多关于JavaScript代码阻塞页面渲染避免方法的资料请关注脚本之家其它相关文章!
