必须用defer:脚本需操作dom或依赖其他脚本时;async仅适用于自包含、无dom/依赖的脚本;type="module"隐式defer但有cors和兼容性限制;动态插入需手动处理onload/onerror。

脚本加了 async 却拿不到 document.getElementById('app')?不是加载慢,是执行时机错了——关键不在“要不要快”,而在“能不能碰 DOM”和“有没有依赖”。
什么时候必须用 defer
脚本要操作 DOM 或依赖其他脚本导出的变量时,defer 是唯一安全选项。
- 典型场景:
Vue.createApp挂载#app、绑定按钮点击事件、调用document.querySelector获取节点、先加载lodash.js再用_.debounce -
defer脚本一定在 HTML 解析完成、DOMContentLoaded触发前执行,DOM 树完整可用 - 多个
defer脚本严格按 HTML 中出现顺序执行,适合构建依赖链(如utils.js必须写在main.js前) - 注意:
defer只对带src的外部脚本生效;<script defer>console.log(1)</script>中的defer被浏览器完全忽略
什么时候可以放心用 async
async 仅适用于真正自包含、不读写 DOM、也不被其他脚本依赖的脚本。
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
- 典型例子:
analytics.js、ad-banner.js、error-tracker.js,它们只调navigator.sendBeacon或fetch上报,失败不影响主逻辑 -
async脚本下载完立刻执行,可能发生在都没解析到一半的时候,document.getElementById('main')返回null是常态 - 多个
async脚本谁先下完谁先跑,顺序不可控——所以别把jquery.js和plugin.js都设成async - 如果同时写了
<script async defer src="a.js"></script>,现代浏览器会直接忽略defer,按async行为执行
type="module" 脚本的隐式 defer 行为
ES 模块脚本天然具备 defer 特性,但有额外约束,不能简单当成“更高级的 defer”来用。
-
type="module"脚本默认不阻塞解析、延迟执行、保序,且支持import语法 - 模块路径必须是相对或绝对 URL,不能是纯文件名;CDN 上的模块脚本必须是 CORS 可访问的,否则静默失败
- 旧版 IE 完全不执行
type="module",需提供降级 fallback(如传统defer脚本) - 内联模块(
<script type="module"></script>)也受 same-origin 限制,无法跨域执行
动态插入脚本的适用场景与坑点
当脚本不该随页面一起加载,而是由用户行为或运行时条件触发时,必须用 JS 动态插入。
- 常见用例:评论区滚动到底部才加载、地图组件点击后引入、A/B 测试分流加载不同 SDK
- 关键控制点:必须手动监听
onload和onerror,否则加载失败静默,调试困难 -
script.async = false可显式关闭异步,但必须确保插入时机合理(比如插入到后且 DOM 尚未开始解析) - 示例:
const s = document.createElement('script'); s.src = '/js/chart.js'; s.onload = () => initChart(); document.head.appendChild(s);
真正复杂的点不在属性本身,而在脚本之间的依赖是否被显式声明;async 和 defer 都不解决依赖,只控制加载与执行时机。最容易被忽略的是:内联脚本永远同步执行,它会卡在它出现的位置,不管前后有没有 async 或 defer。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










