defer脚本在HTML解析完成、DOMContentLoaded触发前按书写顺序执行,可安全操作DOM;async脚本下载完立即执行,不保证顺序且DOM可能未就绪。

脚本位置和属性直接决定 DOMContentLoaded(即 jQuery 的 ready)能否及时触发,而不是“放哪儿更合适”这种模糊判断——关键看它是否阻塞解析、是否依赖 DOM 就绪状态。
默认 script 标签为什么让 document.getElementById 返回 null
没加 async 或 defer 的 <script> 会同步下载、解析、执行,期间 HTML 解析完全暂停。哪怕只有一行 console.log("hi"),也会把 DOMContentLoaded 推迟到它执行完。
- 常见现象:
document.getElementById("app")返回null、Vue 初始化失败、按钮点击无响应 - DevTools Network 面板里
DOMContentLoaded时间戳明显滞后于 HTML 完成线 - 第三方 SDK(如
analytics.js)放在<head>且没defer,首屏渲染直接卡住几百毫秒 -
<script src="polyfill.js"></script>在<head>里,<main>内容迟迟不渲染
defer 脚本执行时机与 DOMContentLoaded 的先后关系
defer 脚本不会阻塞解析,但它的执行严格发生在 DOM 构建完成之后、DOMContentLoaded 触发之前——也就是说,这个事件是 defer 脚本执行完才发的,不是并发的。
- 你在
defer脚本里能安全调用document.getElementById、element.addEventListener - 但不能假设 CSS 已生效或图片已加载:
getComputedStyle(element)可能返回初始值,document.images[0].complete可能还是false - 多个
defer脚本按 HTML 中出现顺序执行,和下载完成时间无关;<script src="a.js" defer></script>和<script src="b.js" defer></script>,即使 b.js 下载更快,也一定等 a.js 执行完再执行 b.js -
async和defer不能共存;若同时写,async优先级更高,defer被静默忽略
async 脚本为何不能依赖 DOMContentLoaded
async 脚本下载时不阻塞解析,但一旦下载完成就立刻执行——不管 DOM 是否构建完毕。它可能在 DOMContentLoaded 前、中、后任意时刻运行,完全不可预测。
立即学习“前端免费学习笔记(深入)”;
- 不能在
async脚本里注册DOMContentLoaded监听器 - 不能在里面操作 DOM 元素(除非加兜底判断,比如
if (document.body) { ... }) - 不能指望它和其他脚本有执行顺序;Webpack 默认输出不带
defer,需在html-webpack-plugin配置scriptLoading: 'defer'
DOMContentLoaded 和 load 的本质区别在哪
DOMContentLoaded 是 DOM 树构建完成就触发,不等样式表、图片、iframe 加载;load 是整个页面及所有依赖资源(包括图片、CSS、子框架)全部加载完毕才触发。
-
DOMContentLoaded必须等待其所属<script>之前的样式表加载解析完成才会触发 -
window.onload或window.addEventListener('load', ...)才是正确的原生写法;document.onload在大多数浏览器中无效 - jQuery 的
$(function(){})等价于$(document).ready(...),底层就是监听DOMContentLoaded;IE8 不支持该事件,可用document.onreadystatechange+document.readyState === 'interactive'兜底
最容易被忽略的是:DOM 就绪 ≠ 页面可用。DOMContentLoaded 后 DOM 存在,但字体、图片、CSS 可能还没生效,getBoundingClientRect() 或 offsetHeight 可能不准;真正要等视觉稳定,得结合 requestIdleCallback 或 performance.getEntriesByType('paint')。



















