async对打包后的bundle.js无效,因其虽异步下载但会过早执行导致DOM访问失败;应改用defer确保DOM就绪后按序执行,内联脚本需用DOMContentLoaded监听,preload仅加速下载不改变执行时机。

async属性对打包后bundle.js根本不起作用
Webpack、Vite 或 esbuild 打包生成的 bundle.js 是一个自包含的执行单元(IIFE 或 ESM 封装体),浏览器把它当作普通脚本加载。此时加 async 属性,它确实会异步下载,但“异步执行”的副作用毫无意义——因为整个 bundle 通常依赖 DOM 就绪、全局库(如 Vue)、甚至其他脚本的初始化状态。一旦 bundle.js 用 async 加载,大概率在 document.body 还没解析出来时就执行,报 Cannot read property 'querySelector' of null。
常见错误现象:把 Webpack 输出的 main.js 写成 <script async src="main.js"></script>,本地刷新偶尔正常,CI 环境或弱网下必现白屏或初始化失败。
-
async只改变下载和执行时机,不解决依赖问题;打包产物本身不具备“可独立异步执行”的前提 - 即使脚本体积小,只要它调用
document.getElementById、new Vue或monaco.editor.create,就不是真正独立的 - 构建工具不会重写你的 HTML 属性,
async被照搬过去,但语义已错配
defer才是打包脚本的实际正确选择
对绝大多数打包产物(main.js、app.js),应该用 defer 替代 async。它不阻塞 HTML 解析,又保证在 DOM 构建完毕后、按顺序执行——这正好匹配打包脚本的典型依赖链:DOM → 框架 → 应用逻辑。
注意几个硬性前提:
立即学习“前端免费学习笔记(深入)”;
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
-
defer只对带src的外部脚本生效;<script defer>init();</script>中的defer会被忽略 - 如果用了
type="module",再加defer是冗余的(模块脚本默认具defer语义),但不能混用:Webpack 打包产物不应设type="module" - 多个
defer脚本必须严格按 HTML 顺序书写,否则依赖关系会断裂(比如vue.js必须在app.js之前)
内联初始化代码不能靠async/defer“偷懒”
有些项目会在 HTML 里直接写一段内联 JS 做初始化,比如 <script>mountApp();</script>,然后试图加 async 或 defer 让它“晚点执行”。这是无效的:async 和 defer 对内联脚本完全不生效,浏览器直接忽略属性,脚本仍同步执行、立即阻塞解析。
正确做法只有一种:把逻辑延迟到 DOM 就绪后触发。
- 用
document.addEventListener('DOMContentLoaded', mountApp)—— 最通用、兼容性好 - 如果脚本已在
底部,且确定 DOM 已就绪,可直接调用,无需额外包装 - 避免
window.onload:它等图片、字体等所有资源,太晚;也别轮询document.body,低效且不可靠
HTTP/2 推送或 preload 不能替代 defer 语义
有人想用 <link rel="preload" href="main.js" as="script"> 或 HTTP/2 Server Push 提前拉取脚本,以为这样就能放开用 async。不行。预加载只加速下载,不改变执行时机。async 脚本仍可能在 标签还没解析到时就执行。
真正要的是执行可控,不是下载快。
-
preload+defer是合理组合:提前下载 + 延后执行 -
preload+async是危险组合:下载快了,执行更早、更容易出错 - 动态拼接的路径(如
src="app." + VERSION + ".js")无法被预扫描识别,preload必须写死路径才能生效
最常被忽略的一点:打包产物是否真的“无依赖”?只要它操作 DOM、调用第三方库、或读取 window.__INITIAL_STATE__,它就不是 async 友好的。别被“异步加载”这个词带偏,关键不在“是否并行下载”,而在“何时执行、能否成功”。


















