DOMContentLoaded 应尽早触发,但常因阻塞 CSS、同步脚本等被延迟;onload 本就最晚,不应优化而应慎用,仅在需所有资源加载完成时使用。

DOMContentLoaded 无法提前,但可以避免被拖慢;onload 本身就不该追求“快”,它本就是最后一步。 你看到的“首屏白屏久但 DOMContentLoaded 已触发”,不是事件错了,而是渲染被卡在 CSSOM 或 JS 执行上——DOM 就绪 ≠ 页面可交互 ≠ 视觉可见。
DOMContentLoaded 被延迟的常见原因
它本应在 HTML 解析完成时立刻触发,但实际常被以下情况拉长:
-
<link rel="stylesheet">阻塞解析:浏览器遇到未加载完的 CSS,会暂停 DOM 构建,直到该 CSS 下载并解析完毕(哪怕它后面还有 HTML) -
<script>没加defer或async:同步脚本会中断 HTML 解析,下载+执行完才继续,DOM 构建被迫等待 - 内联
<script>中调用document.write():直接重写文档流,强制重新解析,DOM 构建从头开始 - CSS 中包含
@import或阻塞型字体加载(如未加font-display: swap):间接延长 CSSOM 构建时间,进而推迟 DOMContentLoaded
window.onload 不该被“优化”,而应被“慎用”
window.onload 的触发时机由浏览器严格定义:所有子资源(<img>、<iframe>、<link rel="stylesheet">、<script src> 等)的 load 事件全部完成才算数。这意味着:
- 一张 5MB 的未压缩 banner 图,会让整个
window.onload卡住几十秒,哪怕其余内容早已就绪 - 第三方广告 iframe 加载失败或超时,
window.onload就永远不会触发(除非手动 fallback) - 现代 SPA 中,
window.onload对路由切换无意义——它只对首次完整页面加载有效 - 真正需要它的场景极少:
<img>尺寸读取、<canvas>绘制、依赖图片宽高的布局计算、全量资源埋点
如何判断该用 DOMContentLoaded 还是 onload
核心看你要操作的对象是否“已加载完成”:
立即学习“前端免费学习笔记(深入)”;
- 要绑定按钮点击事件?用
DOMContentLoaded—— DOM 节点存在即可,不等图片 - 要读取
document.getElementById("hero-img").offsetWidth?必须等图片加载,用<img>.addEventListener("load", handler),而不是window.onload - 要初始化一个依赖 WebFont 的文字渲染器?监听
document.fonts.load(),而非等window.onload - 要做首屏性能上报?用
performance.getEntriesByType("navigation")[0].domContentLoadedEventEnd,比监听事件更准
实操建议:让 DOMContentLoaded 尽早稳定触发
不是靠“加速”,而是靠“不干扰”:
- 把所有非关键
<script>标签加上defer属性(尤其是那些操作 DOM 的) - 关键 CSS 内联到
<head>,或用<link rel="preload" as="style">提前拉取 - 避免在
<head>里放同步<script>,尤其不要在<link rel="stylesheet">前 - 检查是否有隐藏的
<iframe src="slow-ads.com">,它也会拖慢window.onload,但不影响 DOMContentLoaded - 用 Chrome DevTools 的
Performance面板,看DOMContentLoaded时间点前后,是否卡在 “Parse HTML” 或 “Recalculate Style” 阶段
最易被忽略的一点:DOMContentLoaded 并不保证 CSSOM 已构建完成——如果 JS 在这个事件里读取 getComputedStyle(el).width,仍可能拿到 auto 或初始值。真要样式就绪,得监听 document.addEventListener("readystatechange", () => { if (document.readyState === "interactive") ... }),但这行为在 IE/Edge 有差异,不如直接用 requestAnimationFrame + 检查 computed style 是否变化来得可靠。



















