async脚本执行时document.getelementbyid常返回null,因其下载完立即执行,不等html解析完成,目标dom节点可能尚未创建;defer脚本则确保在dom解析完成、domcontentloaded触发前按序执行,可安全操作完整dom。

加 async 或 defer 不等于“不阻塞”,只是把阻塞点挪到不同阶段——关键不是“要不要异步”,而是“脚本到底依赖什么”。
async 脚本执行时 document.getElementById 为什么常返回 null
因为 async 脚本一旦下载完成就立即执行,此时 HTML 解析可能刚到 就中断了, 甚至还没开始解析,更别说目标 DOM 节点了。
- 典型错误:
<script async src="ui.js"></script>里调用document.getElementById('app'),但#app在底部 - 根本原因:
async不保证执行时机,只保证下载不阻塞解析 - 适用边界:脚本必须完全不读写 DOM、不依赖任何全局变量、失败也不影响主流程(如
analytics.js) - 验证方法:在脚本开头加
console.log(document.readyState),大概率是loading或interactive
defer 脚本为什么有时拿不到计算样式
defer 确保脚本在 DOM 解析完成后、DOMContentLoaded 触发前执行,但它不等 CSSOM 构建完成——如果关键样式来自未加载完的 <link rel="stylesheet">,getComputedStyle 就会返回空值或默认值。
- 常见现象:轮播图初始化后宽度为 0,因为 CSS 还没生效
- 解决路径:把关键 CSS 提前
<link rel="preload" as="style" href="critical.css">,再动态插入<link rel="stylesheet"> - 注意:
defer对内联脚本无效,<script defer>console.log(1)</script>中的defer被浏览器忽略 - IE9– 完全不支持
defer,降级为同步加载,需用document.write或动态插入兜底
type="module" 脚本为什么能安全放 head 里操作 DOM
type="module" 默认具备 defer 行为:异步下载、DOM 解析完后执行、顺序保证;但它还多一层保障——模块执行前,浏览器已确保整个 HTML 文档解析完毕且 DOM 树完整。
- 可直接在
写:<script type="module" src="main.mjs"></script>,无需包裹DOMContentLoaded回调 - 模块自动启用严格模式,且支持顶层
await,适合等待customElements.define或fetch初始化数据 - 限制:模块路径必须是绝对 URL 或以
/、./、../开头;不能用file://协议直接打开 - 兼容性:Chrome 61+、Firefox 60+、Safari 11+、Edge 16+;旧浏览器需转译 +
nomodulefallback
动态插入 script 标签如何避免静默失败
手动创建 <script></script> 是唯一能精确控制加载时机的方式,但默认不报错——脚本 404 或语法错误都不会触发异常,只能靠监听事件捕获。
- 必须设置
src后再 append:script.src = 'a.js'; document.head.appendChild(script);,否则部分浏览器同步执行 - 务必监听
onload和onerror:script.onload = () => console.log('ok'); script.onerror = () => console.error('load failed'); - 若需保序(如先加载
lodash.js再加载业务脚本),用onload回调链,而非多个async=true并行 -
script.async = false可模拟defer行为,但仅在插入到且 DOM 尚未开始解析时有效
最易被忽略的一点:async/defer 对 CSS 完全无效,<link rel="stylesheet"> 始终阻塞渲染树构建——哪怕你把所有 JS 都 async 了,一个未优化的 vendor.css 仍会让首屏白屏几秒。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











