技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

window.onload 和 DOMContentLoaded 的区别

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/59-window onload 和 DOMContentLoaded 的区别
本文目录10 个章节
  1. 一、先给结论
  2. 二、HTML 文档的大致过程
  3. 三、DOMContentLoaded 事件
  4. 四、window.onload 事件
  5. 五、原文背景色示例的现代写法
  6. 六、观察事件顺序
  7. 七、readystatechange 与 readyState
  8. 八、如何选择
  9. 总结
  10. 参考资料

window.onloadDOMContentLoaded 的区别

Category(分类): JavaScript Status: 已整理(2026)

原文:window onload 和 DOMContentLoaded 的区别。本文保留原文对两个事件、DOM 解析过程、脚本延迟和事件绑定时机的说明,并修正“DOMContentLoaded 完全不受样式表影响”“load 等于所有资源绝对加载完成”等过于绝对的表述。

一、先给结论

事件主要含义通常等待什么通常不等待什么
DOMContentLoadedHTML 已解析成 DOM,且需要等待的 deferred/module 脚本已执行HTML 解析、parser-blocking 脚本造成的解析延迟、defer 和普通模块脚本普通图片、视频、iframe 等外部资源
windowload页面及其相关非懒加载资源完成加载DOM、样式表、脚本、图片、iframe 等页面资源懒加载资源等不参与当前加载过程的资源

DOMContentLoaded 关注“DOM 是否可以使用”;load 关注“页面资源是否基本全部就绪”。因此初始化交互通常不必等待 load,而需要图片尺寸、字体或 iframe 完成时才考虑 load

二、HTML 文档的大致过程

简化后可以这样理解:

  1. 浏览器接收并解析 HTML;
  2. 解析过程中发现样式表、脚本、图片等资源并发起请求;
  3. 普通 parser-inserted classic script 可能暂停解析,获取并执行后继续;
  4. HTML 解析完成,文档进入 interactive 状态;
  5. defer 和普通模块脚本执行;
  6. 触发 DOMContentLoaded
  7. 其他页面资源继续或完成加载;
  8. 触发 windowload,文档通常进入 complete 状态。

这不是所有资源和脚本类型的唯一顺序:async 脚本可以在准备好后自行执行,模块依赖和顶层 await 也会影响评估时间。更准确的说法是:DOMContentLoaded 发生在 DOM 解析完成和需要等待的 deferred/module 脚本执行之后,而 load 还要等待更广泛的资源。

三、DOMContentLoaded 事件

最基本的写法:

document.addEventListener('DOMContentLoaded', () => {
  const app = document.querySelector('#app')
  app.textContent = 'DOM is ready'
})

它不是 document.ready。后者是 jQuery 曾经提供的便捷 API;原生浏览器没有 document.ready 这个标准事件。

3.1 它不等待普通图片

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <title>DOMContentLoaded demo</title>
  </head>
  <body>
    <img src="/large-image.jpg" alt="demo">

    <script>
      document.addEventListener('DOMContentLoaded', () => {
        console.log('DOM 已解析,图片可能仍在加载')
      })

      window.addEventListener('load', () => {
        console.log('页面资源加载完成,图片通常已经可用')
      })
    </script>
  </body>
</html>

3.2 样式表的边界

入门文章常写“DOMContentLoaded 不等待样式表”。更精确的理解是:事件本身不把所有样式表都列为直接等待条件,但样式表可能影响脚本执行和事件时机。

例如,parser-blocking classic script 可能要等待此前发现的阻塞样式表;该脚本又会阻塞 HTML 解析,因此会间接推迟 DOMContentLoadeddefer 和模块脚本也可能等待其需要的样式或模块依赖完成。

3.3 同步脚本会延迟 DOM 解析

<script>
  document.addEventListener('DOMContentLoaded', () => {
    console.log('DOMContentLoaded')
  })

  for (let index = 0; index < 100000000; index += 1) {
    // 长时间同步任务会阻塞解析器和主线程
  }
</script>

循环期间主线程没有机会继续解析或派发事件,所以 DOMContentLoaded 会等这段同步任务结束。不要在生产代码中使用这种忙等方式;它只用于说明事件为什么会被推迟。

3.4 异步加载脚本可能错过事件

如果脚本以 async、动态插入或动态 import 的方式加载,它运行时 DOMContentLoaded 可能已经发生。稳妥的初始化模式是同时检查 readyState

function init() {
  console.log('开始初始化')
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init, { once: true })
} else {
  init()
}

JavaScript 的 run-to-completion 语义保证了 readyState 检查与添加监听器之间不会被另一个事件任务插入,因此这种写法不存在通常所说的竞态窗口。

四、window.onload 事件

可以使用事件监听器:

window.addEventListener('load', () => {
  console.log('页面及相关资源已加载')
})

也可以设置事件处理器属性:

window.onload = () => {
  console.log('页面已加载')
}

window.onload = ... 后一次赋值会覆盖前一次处理器;需要多个模块同时监听时,应使用 addEventListener

load 通常等待主文档依赖的样式表、脚本、图片和 iframe 等资源。它不是“网络上所有东西都完成”的保证:懒加载资源、用户交互后才开始的资源以及不属于当前加载过程的资源可能不影响初始 load

原文提到 Flash;Flash 已停止支持,现代页面应把关注点放在图片、字体、iframe、脚本和其他实际资源上。

五、原文背景色示例的现代写法

原文通过在 window.onload 中查找 #bg 并设置背景色,说明等待页面加载后再操作元素。示例可以整理为:

<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <title>load demo</title>
    <style>
      #bg {
        width: 100px;
        height: 100px;
        border: 2px solid;
      }
    </style>
  </head>
  <body>
    <div id="bg"></div>

    <script>
      window.addEventListener('load', () => {
        document.querySelector('#bg').style.backgroundColor = 'yellow'
      })
    </script>
  </body>
</html>

原文背景色示例一

图片来源:原文配图

不过,如果代码只依赖 DOM 元素而不依赖图片等资源,使用 load 可能等待过久。下面的写法通常更合适:

document.addEventListener('DOMContentLoaded', () => {
  document.querySelector('#bg').style.backgroundColor = 'yellow'
})

原文背景色示例二

图片来源:原文配图

甚至可以把脚本放在目标元素之后:

<div id="bg"></div>
<script>
  document.querySelector('#bg').style.backgroundColor = 'yellow'
</script>

六、观察事件顺序

<body>
  <p>测试事件顺序</p>

  <script>
    console.log('同步脚本执行')

    document.addEventListener('DOMContentLoaded', () => {
      console.log('DOMContentLoaded 回调')
    })

    window.addEventListener('load', () => {
      console.log('load 回调')
    })
  </script>
</body>

在没有其他异步因素的简单页面中,通常能看到:

同步脚本执行
DOMContentLoaded 回调
load 回调

使用 defer 的外部脚本一般在 DOM 解析后、DOMContentLoaded 前执行:

<head>
  <script defer src="/js/app.js"></script>
</head>

使用 async 时,脚本不保证和 DOMContentLoaded 的相对顺序:

<script async src="/js/analytics.js"></script>

如果 analytics 脚本下载很快,它可能在 DOMContentLoaded 前执行;如果下载很慢,DOMContentLoaded 可能先发生。不要把某一次网络截图当成规范保证。

七、readystatechangereadyState

需要观察文档阶段时可以监听 readystatechange

document.addEventListener('readystatechange', () => {
  console.log(document.readyState)
})

常见状态包括:

  • loading:HTML 仍在解析;
  • interactive:文档已经解析完成,但 deferred/module 脚本和 DOMContentLoaded 相关流程可能尚未结束;
  • complete:文档及其相关加载过程基本完成,随后会触发或已经触发 load

readyState 适合配合前面的“事件可能已经发生”模式,但它不能替代对图片、字体、iframe 等具体资源的等待。如果只需要某张图片完成,直接监听该元素的 load 更准确:

const image = document.querySelector('img')

if (image.complete) {
  console.log('图片已经结束加载过程')
} else {
  image.addEventListener('load', () => console.log('图片加载成功'), { once: true })
  image.addEventListener('error', () => console.error('图片加载失败'), { once: true })
}

八、如何选择

  • 只操作已经写在页面中的 DOM:脚本放在 body 底部、使用 defer/模块脚本,或监听 DOMContentLoaded
  • 依赖图片尺寸、iframe 或其他页面资源:使用 windowload 或具体资源的 load
  • 异步/动态脚本:检查 document.readyState,避免错过 DOMContentLoaded
  • 多个监听者:使用 addEventListener,不要反复覆盖 window.onload
  • 不要为了 DOM 就绪无条件等待所有图片和字体,这会推迟交互;
  • 不要把 DOMContentLoaded 和 jQuery readywindow.onload 当作同一个事件。

总结

DOMContentLoaded 表示 DOM 已经解析完成,适合初始化页面结构和绑定交互;window.load 表示页面加载过程中的更广泛资源已经完成,适合依赖外部资源的场景。脚本类型、样式表、模块依赖、async/defer 和动态加载都会改变时机,判断时应从“它需要等待什么资源”出发,而不是死记一个固定的时间顺序。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS