DOMContentLoaded 与 Load 事件的区别

1. 核心定义

(1) DOMContentLoaded

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。

触发时机:DOM 树构建完毕。 关键点:此时 JS 可以安全地访问和操作所有 DOM 节点。

(2) Load (window.onload)

所有资源(HTML、DOM、CSS、图片、脚本、视频、iframe 等)都加载完成时,load 事件被触发。

触发时机:页面完全彻底加载完毕。

2. 详细执行流程

  1. 浏览器请求 HTML 文档。
  2. 解析 HTML,构建 DOM 树。
  3. 遇到 <script>(无 async/defer):暂停解析,下载并执行 JS。
  4. 遇到 CSS:异步下载,不阻塞 DOM 构建(但会阻塞渲染)。
  5. HTML 解析完毕 -> 触发 DOMContentLoaded
  6. 继续加载剩余的外部资源(图片、视频、iframe)。
  7. 所有资源加载完毕 -> 触发 load

3. 面试场景分析

Q: 为什么脚本通常放在<body> 底部?

为了不阻塞 HTML 的解析,让 DOM 尽快构建,使用户能更快看到页面内容。这也间接让 DOMContentLoaded 更早触发。

Q: CSS 加载会阻塞DOMContentLoaded 吗?

理论上不会,因为 DOMContentLoaded 只关注 HTML 解析。 实际上可能会:如果 JS 脚本位于 CSS 之后,浏览器为了保证 JS 能获取到正确的样式(有可能 JS 会读取 CSS),会等待 CSS 加载完毕再执行 JS。而 JS 又阻塞了 DOM 解析。此时 CSS 变相阻塞了 DOMContentLoaded

Q: 哪个更适合性能监控?

  • DOMContentLoaded: 衡量用户何时可以开始交互(First Interactive 的近似值)。
  • Load: 衡量页面何时完全就绪。对于图片很多的页面,Load 时间可能很长,但用户早就开始看了。

4. 代码示例

document.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM 完全解析和加载');
});

window.addEventListener('load', (event) => {
    console.log('页面所有资源加载完毕');
});

实战技巧:在开发第三方库或插件时,为了确保安全操作 DOM,通常会判断 document.readyState。如果是 interactivecomplete,直接执行;否则监听 DOMContentLoaded

// jQuery 的 ready 实现原理简写
function ready(fn) {
    if (document.readyState !== 'loading') {
        fn();
    } else {
        document.addEventListener('DOMContentLoaded', fn);
    }
}