文档:DOMContentLoaded 事件

Baseline 已广泛支持

此特性已相当成熟,可在许多设备和浏览器版本上使用。自 ⁨2015 年 7 月⁩以来,各浏览器均已提供此特性。

当 HTML 文档被完全解析,并且所有延迟脚本(<script defer src="…"><script type="module">)都已下载和执行时,会触发 DOMContentLoaded 事件。它不等待其他内容(如图像、子框架和异步脚本)完成加载。

DOMContentLoaded 不会等待样式表加载,但是延迟脚本 等待样式表,并且 DOMContentLoaded 事件会在延迟脚本之后排队。此外,非延迟或非异步脚本(例如 <script>)会等待已解析的样式表加载。

另一个事件 load 应该只用于检测页面是否完全加载。将 load 用于 DOMContentLoaded 更合适的地方是一个常见的错误。

通常,为了避免在它所操作的 DOM 完全构建之前运行脚本,你只需将脚本放在文档主体的末尾,紧邻结束的 </body> 标签之前,而无需将其包装在事件监听器中。

此事件不可取消。

语法

addEventListener() 等方法中使用事件名称。

js
addEventListener("DOMContentLoaded", (event) => { })

注意:此事件没有 onDOMContentLoaded 事件处理程序属性。

事件类型

一个通用的 Event

示例

基本用法

js
document.addEventListener("DOMContentLoaded", (event) => {
  console.log("DOM fully loaded and parsed");
});

延迟 DOMContentLoaded

html
<script>
  document.addEventListener("DOMContentLoaded", (event) => {
    console.log("DOM fully loaded and parsed");
  });

  for (let i = 0; i < 1_000_000_000; i++);
  // This synchronous script is going to delay parsing of the DOM,
  // so the DOMContentLoaded event is going to launch later.
</script>

检查加载是否已完成

有时你的脚本可能会在 DOMContentLoaded 事件已经触发后运行。这通常发生在脚本异步运行的情况下。常见场景包括:

  • 文档加载后动态导入的模块。
  • 通过 <script async> 包含的脚本。
  • 动态注入到页面中的脚本。
  • 异步操作(例如 await fetch(...))之后恢复的代码,包括模块中的顶级 await 之后。

在这些情况下,你应该在添加 DOMContentLoaded 监听器之前检查文档的 readyState,否则你的设置逻辑可能根本不会执行。对于初始标记中已经存在的同步脚本(没有 async),这种情况不会发生。文档在触发 DOMContentLoaded 之前等待脚本执行,因此你始终可以确保监听器中的设置逻辑将执行。

单独考虑以下脚本文件

js
function doSomething() {
  console.info("DOM loaded");
}

if (document.readyState === "loading") {
  // Loading hasn't finished yet
  document.addEventListener("DOMContentLoaded", doSomething);
} else {
  // `DOMContentLoaded` has already fired
  doSomething();
}

脚本无法强制其由 HTML 包含的方式。如果它通过 <script async> 包含,或者它是动态注入的,那么当它执行时,DOMContentLoaded 已经触发。为了确保 doSomething() 在脚本加载时始终运行,我们需要有两条路径,一条是如果文档已经加载则立即运行 doSomething,另一条是在文档加载后运行 doSomething

注意:这里没有竞态条件——文档不可能在 if 检查和 addEventListener() 调用之间加载。JavaScript 具有运行到完成语义,这意味着如果文档在事件循环的某个特定时刻正在加载,它就不能在下一个周期之前加载完成,届时 doSomething 处理程序已经附加并将被触发。

注意:document.readyState 在 HTML 解析器完成之后,但在带有 defertype="module" 的脚本执行之前设置为 "interactive"DOMContentLoaded 在这些脚本执行之后,但在带有 async 的脚本执行之前触发。document.readyState 在异步脚本执行之后设置为 "complete"。这意味着在延迟和模块脚本执行期间,document.readyState"interactive",但仍然可以附加 DOMContentLoaded 监听器并使其像往常一样触发。实际上,除非 doSomething() 依赖于其他延迟/模块脚本设置的一些全局状态,否则稍微早一点执行 doSomething() 没什么问题。

实时示例

HTML

html
<div class="controls">
  <button id="reload" type="button">Reload</button>
</div>

<div class="event-log">
  <label for="eventLog">Event log:</label>
  <textarea
    readonly
    class="event-log-contents"
    rows="8"
    cols="30"
    id="eventLog"></textarea>
</div>

JavaScript

js
const log = document.querySelector(".event-log-contents");
const reload = document.querySelector("#reload");

reload.addEventListener("click", () => {
  log.textContent = "";
  setTimeout(() => {
    window.location.reload(true);
  }, 200);
});

window.addEventListener("load", (event) => {
  log.textContent += "load\n";
});

document.addEventListener("readystatechange", (event) => {
  log.textContent += `readystate: ${document.readyState}\n`;
});

document.addEventListener("DOMContentLoaded", (event) => {
  log.textContent += "DOMContentLoaded\n";
});

结果

规范

规范
HTML
# 停止解析

浏览器兼容性

另见