文档:DOMContentLoaded 事件
当 HTML 文档被完全解析,并且所有延迟脚本(<script defer src="…"> 和 <script type="module">)都已下载和执行时,会触发 DOMContentLoaded 事件。它不等待其他内容(如图像、子框架和异步脚本)完成加载。
DOMContentLoaded 不会等待样式表加载,但是延迟脚本 会 等待样式表,并且 DOMContentLoaded 事件会在延迟脚本之后排队。此外,非延迟或非异步脚本(例如 <script>)会等待已解析的样式表加载。
另一个事件 load 应该只用于检测页面是否完全加载。将 load 用于 DOMContentLoaded 更合适的地方是一个常见的错误。
通常,为了避免在它所操作的 DOM 完全构建之前运行脚本,你只需将脚本放在文档主体的末尾,紧邻结束的 </body> 标签之前,而无需将其包装在事件监听器中。
此事件不可取消。
语法
在 addEventListener() 等方法中使用事件名称。
addEventListener("DOMContentLoaded", (event) => { })
注意:此事件没有 onDOMContentLoaded 事件处理程序属性。
事件类型
一个通用的 Event。
示例
基本用法
document.addEventListener("DOMContentLoaded", (event) => {
console.log("DOM fully loaded and parsed");
});
延迟 DOMContentLoaded
<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 之前等待脚本执行,因此你始终可以确保监听器中的设置逻辑将执行。
单独考虑以下脚本文件
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 解析器完成之后,但在带有 defer 或 type="module" 的脚本执行之前设置为 "interactive"。DOMContentLoaded 在这些脚本执行之后,但在带有 async 的脚本执行之前触发。document.readyState 在异步脚本执行之后设置为 "complete"。这意味着在延迟和模块脚本执行期间,document.readyState 是 "interactive",但仍然可以附加 DOMContentLoaded 监听器并使其像往常一样触发。实际上,除非 doSomething() 依赖于其他延迟/模块脚本设置的一些全局状态,否则稍微早一点执行 doSomething() 没什么问题。
实时示例
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
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 # 停止解析 |
浏览器兼容性
加载中…