defer和async的区别:前端加载优化的关键细节

打开一个网页,有时候内容唰一下就出来了,有时候却要等半天。你可能觉得是网速问题,但其实背后脚本的加载方式起了关键作用。比如你在页面里引入了几个 JavaScript 文件,用错了方法,哪怕代码再少,也可能拖慢整个页面。

script 标签的默认行为

当你在 HTML 里写上 <script src="app.js"></script>,浏览器遇到这行就会停下来,先去下载这个 JS 文件,执行完才能继续往下解析页面。这就是所谓的“阻塞渲染”。如果这个脚本特别大,或者服务器响应慢,用户看到的可能就是一片空白,啥也没有。

async:异步加载,执行时机靠后

给 script 加上 async 属性,比如 <script async src="analytics.js"></script>,浏览器会在下载这个文件时不阻塞页面解析。但一旦下载完成,不管页面有没有解析完,都会立刻暂停解析,先执行这段脚本。

这适合那些不依赖 DOM、也不被其他脚本依赖的独立脚本,比如统计代码。它不在乎什么时候执行,只要最终跑起来就行。但如果你的脚本需要操作页面元素,而这时 DOM 还没生成完,那就容易出错。

defer:延迟执行,按顺序跑

换成 defer,写成 <script defer src="main.js"></script>,效果就不一样了。浏览器异步下载这个文件,但不会马上执行,而是等到整个 HTML 文档解析完毕,DOM 构建完成后才按顺序执行。

多个 defer 脚本会按照它们在 HTML 中出现的顺序依次运行,不会打乱逻辑。这对依赖 DOM 或彼此依赖的脚本特别友好。比如你先加载工具函数,再加载主逻辑,用 defer 就能保证顺序正确。

实际场景对比

假设你在一个博客页面引入两个脚本:一个是 Google 统计(analytics.js),另一个是评论区初始(comments.js),后者需要等页面结构完整才能挂载。

这时候,analytics.js 用 async,因为它独立运行,早点晚点执行都没关系;而 comments.js 必须等 DOM 出来,就得用 defer。混着用,各司其职,体验自然流畅。

代码示例对比

三种加载方式的写法差异:

<!-- 默认:阻塞解析 -->\n<script src="old.js"></script>\n\n<!-- async:异步下载,下载完立即执行 -->\n<script async src="analytics.js"></script>\n\n<!-- defer:异步下载,文档解析完再执行 -->\n<script defer src="app.js"></script>

注意:async 和 defer 在动态创建 script 标签时也能生效,比如用 document.createElement('script') 手动添加,加上相应属性一样起作用。

浏览器支持与使用建议

p>现代浏览器都支持 defer 和 async。如果必须兼容很老的 IE,可以考虑把 script 放在 body 底部作为替代方案。但在今天,绝大多数情况直接用 defer 更稳妥,尤其是你的 JS 要操作页面结构的时候。

async 适合那种“甩出去就不用管”的脚本,比如第三方广告、埋点。而 defer 更像是“我准备好再动手”的类型,适合大多数业务逻辑脚本。