应避免使用 body 的 onload 属性,因其会覆盖其他 onload 逻辑、依赖全部资源加载、无法与 defer/module 协同,且不支持多监听;推荐用 document.addEventListener('DOMContentLoaded', ...) 或 script defer/type="module"。

别用 body 的 onload 属性——它会覆盖其他 onload 逻辑,且无法与现代脚本加载方式(如 defer、module)协同工作。
为什么 body.onload 不可靠
HTML 中的 onload 是事件处理器属性,但直接写在 <body onload="..."> 上存在几个硬伤:
- 同一页面只能有一个
onload属性生效,后续 JS 赋值(如window.onload = fn)会把它覆盖掉 - 它依赖整个页面(含图片、iframe、CSS 等)全部加载完成才触发,实际 DOM 就绪往往早得多
- 如果脚本在
<body>前就执行(比如放在<head>),body元素本身都还没解析出来,属性根本不会绑定 - 不支持事件监听器机制,无法添加多个处理函数,也不支持
once或capture等选项
DOMContentLoaded 是更准的“DOM 加载完成”信号
真正表示 HTML 解析完毕、DOM 树可操作的时间点是 DOMContentLoaded 事件,它不等图片、样式表或 iframe 加载,只等 DOM 构建完成。
推荐写法(兼容性好,无副作用):
立即学习“前端免费学习笔记(深入)”;
document.addEventListener('DOMContentLoaded', () => {
// 这里可以安全操作 DOM
const header = document.querySelector('header');
if (header) header.classList.add('loaded');
});
- 可多次调用
addEventListener,互不干扰 - 即使脚本提前执行(如放在
<head>),事件监听依然有效 - 注意:若页面已加载完成再监听,事件不会重放;可用
document.readyState判断是否需立即执行
什么时候才该用 window.onload?
window.onload 确实有用,但场景很明确:你需要确保所有资源(尤其是图片、外部 CSS、字体)都已下载并渲染就绪。
- 做首屏截图前的等待(如 Puppeteer 场景)
- 需要读取
img.naturalWidth或canvas.toDataURL()等依赖完整资源加载的操作 - 统计“完全加载耗时”,而非“DOM 可用耗时”
写法上优先用 addEventListener,避免覆盖:
window.addEventListener('load', () => {
console.log('所有资源(含图片、CSS)均已加载');
});
不要写 window.onload = ... —— 它会抹掉其他库(如某些分析 SDK)可能注册的同名监听器。
现代替代方案:defer 和 type="module"
如果你只是想让脚本在 DOM 构建完后执行,最轻量的方式其实是把脚本本身交给浏览器调度:
-
<script defer src="app.js"></script>:脚本异步下载,但保证在 DOM 解析完成后、DOMContentLoaded触发前执行 -
<script type="module" src="app.js"></script>:模块脚本默认具有defer行为,且天然支持顶层await
这种写法无需手动监听事件,也不存在监听时机错位问题。但要注意:defer 脚本的执行顺序由 src 出现顺序决定,而 DOMContentLoaded 监听器的执行顺序取决于注册顺序——两者不等价,选哪个取决于你是否需要动态控制执行时机。
真正容易被忽略的是:DOMContentLoaded 不代表 CSS 已解析完成,如果脚本依赖某个 getComputedStyle 值,而对应 CSS 还在加载中,结果可能是空或默认值。这种边界情况得靠 document.fonts.ready 或 CSSStyleSheet.replace() 后的 loading 状态来补全判断。



















