defer属性仅对外部脚本有效,执行于dom构建完成但domcontentloaded前,按html顺序执行且不阻塞解析;内联脚本加defer会被忽略。

在老旧浏览器(如 IE6-IE8)中,defer 属性虽被支持,但它只保证脚本在文档解析完成、DOMContentLoaded 之前执行,并不等同于现代意义上的 DOMReady。它依赖 <script defer></script> 的位置和浏览器对 HTML 解析流程的实现,不能直接替代 document.readyState === 'interactive' 或自定义 DOM 就绪检测。
defer 在旧版 IE 中的实际行为
IE6–IE9 支持 defer,但仅适用于外部脚本(src 存在),且要求脚本必须在 中声明(部分 IE 版本对 中的 defer 脚本忽略该属性)。它确保脚本按顺序执行,且不会阻塞 HTML 解析,但执行时机是“解析完当前文档但尚未构建完完整 DOM 树”——此时部分元素可能还未被创建或未挂载到 document 上。
- 脚本执行时,
document.body可能为null(尤其当 script 在 head 中且 body 尚未解析) -
document.getElementById查找位于后半段的元素大概率失败 - 无法可靠监听
DOMContentLoaded,因该事件在 IE8 及以下不支持
用 defer + 回退检测模拟 DOMReady
可结合 defer 提前加载脚本,再通过轮询或事件试探判断 DOM 是否可用。推荐在 中写:
<script defer src="domready-polyfill.js"></script>
其中 domready-polyfill.js 内容如下:
- 立即检查
document.body和document.documentElement是否存在且已连接 - 若未就绪,使用
doScroll('left')(IE 特有、可靠的 DOM 就绪检测法) - 对非 IE 浏览器,降级为
document.addEventListener('readystatechange')监听interactive
更轻量的 inline 方案(无需外部文件)
若只需简单初始化,可将逻辑内联在 中,利用 defer 延迟执行,并内置检测:
<script defer>
(function() {
function ready(cb) {
if (document.readyState === 'interactive' || document.readyState === 'complete') {
cb();
} else if (document.documentElement.doScroll) {
try {
document.documentElement.doScroll('left');
cb();
} catch (e) {
setTimeout(function() { ready(cb); }, 1);
}
} else {
document.addEventListener('DOMContentLoaded', cb, false);
}
}
ready(function() {
// 这里写你的 DOM 操作代码
console.log('DOM is ready');
});
})();
</script>
注意:此写法需确保脚本在 内,且不依赖尚未解析的 内容。
为什么不推荐纯 defer 作为 DOMReady 替代?
defer 是加载策略,不是就绪信号。它解决的是“何时开始执行”,而非“何时可以安全操作 DOM”。在复杂页面中,即使脚本 deferred,仍可能早于某些元素解析完成就执行,导致 getElementById 返回 null 或事件绑定失效。
- 依赖 HTML 结构顺序,维护成本高
- 无法应对动态插入内容或异步模板渲染场景
- 现代开发中应优先使用模块化加载器或最小化 polyfill(如 domready 库)











