⭐ 推荐:社区规则条款 V1.0

js 页面加载完成事件有哪些

liulian
liulian 初级会员萌新站长
发布于 2026-10-24 09:02 ·1 浏览 ·0 回复
内容摘要

区分 DOMContentLoaded 与 load:前者在 DOM 解析完成时触发,适合执行 DOM 初始化;后者在所有资源加载完成后触发,适合依赖图片等资源的操作,并说明 CSS 会阻塞 DOMContentLoaded、应使用 add

学完这篇,你能分清 JS 里几种“页面加载完成”事件的先后顺序和适用场景,并知道在你的项目里该用哪一个。

第一步:先分清「DOM 就绪」和「页面完全加载」

这一步要建立基本概念,做完你就能判断某个需求该等哪个事件。

浏览器打开一个页面,会经历两个关键节点:

  1. DOM 结构解析完毕:HTML 标签都变成了 DOM 节点,此时可以用 document.querySelector 取到元素,但图片、CSS、iframe 可能还没下载完。
  2. 所有资源加载完毕:图片、样式表、脚本、iframe 全部下载完成。

DOMContentLoaded 对应第 1 个节点,load 对应第 2 个节点。绝大多数操作 DOM 的初始化代码,都应该放在第 1 个节点,这样用户看到界面更快,也不用等一张大图。

注意:CSS 会阻塞 DOMContentLoaded。如果页面上有 <link rel="stylesheet">,浏览器要等样式表加载完才触发 DOMContentLoaded,因为脚本可能需要读取样式。所以别以为它一定很快。

第二步:用 DOMContentLoaded 做初始化

这一步的目标是让 DOM 操作代码在元素存在之后执行,写法如下:

document.addEventListener('DOMContentLoaded', function () {
  const btn = document.querySelector('#submit');
  btn.addEventListener('click', handleSubmit);
});

关键点:

  • 它是 document 上的事件,不是 window 上的。
  • 用 addEventListener 可以绑定多个回调,按注册顺序执行,后绑的不会覆盖先绑的,这一点比 window.onload = fn 好。
  • 事件对象上有 event.target(就是 document),没有 event.detail 之类的额外信息。

如果需要兼容很老的 IE8,才有 document.attachEvent('onreadystatechange', ...) 这套写法,现代项目不用考虑。

注意:如果脚本放在 <body> 末尾,其实此时 DOM 已经解析完了,DOMContentLoaded 可能早就触发过。这时再 addEventListener 是收不到回调的。写库的时候要先检查 document.readyState:

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

第三步:用 window.onload 等所有资源

这一步用来处理必须等图片尺寸、iframe 内容的场景,比如要读取图片的 naturalWidth:

window.addEventListener('load', function () {
  const img = document.querySelector('#cover');
  console.log(img.naturalWidth, img.naturalHeight);
});

区别对比:

事件绑定对象触发时机能否绑定多个
DOMContentLoadeddocumentDOM 解析完成能
loadwindow所有资源加载完能(用 addEventListener)
window.onload = fnwindow同上不能,会覆盖

注意:load 会被一张加载失败的图片拖住吗?不会,加载失败(404)也会触发 load(在 window 上),但图片元素自身的 error 事件会先触发。真正会拖住 window.load 的是长时间不返回的资源,比如卡住的第三方统计脚本。

第四步:看 readystatechange 了解解析进度

这一步是排查和兼容用,document.readyState 有三个值:

  • loading:文档正在解析
  • interactive:文档解析完成,但子资源还在加载(约等于 DOMContentLoaded 之前的那一刻)
  • complete:所有资源加载完成(约等于 load)
document.addEventListener('readystatechange', function () {
  console.log(document.readyState);
});

实际开发中基本不用它做业务初始化,因为它触发次数多、语义不如前两个清晰。它的价值在于:写加载进度提示、或者排查“为什么我的代码没执行”时,打印一下 document.readyState 就知道脚本跑在哪个阶段。

第五步:项目里有 jQuery 时用 ready

这一步针对老项目。jQuery 的写法是:

$(document).ready(function () {
  // 等价写法
});

$(function () {
  // 简写,最常用
});

jQuery 内部做了 readyState 判断,所以不管你的 <script> 放在哪里,$(function(){}) 都能保证执行一次,不会出现第二步说的「事件已错过」问题。这也是它当年流行的一个原因。

注意:$(window).load(fn) 在 jQuery 3.0 起已被移除,要改用 $(window).on('load', fn)。如果你从旧项目升级,这是常见的报错点。

第六步:新项目优先用 defer 脚本

这一步是减少全局事件依赖的现代做法。给 <script> 加 defer 属性,浏览器会并行下载脚本,等 DOM 解析完成后按顺序执行,执行时机就在 DOMContentLoaded 之前:

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

这样一来,app.js 里可以直接写 document.querySelector(...),不需要再包一层事件监听。

对比 async:

  • defer:保证按标签顺序执行,在 DOMContentLoaded 前执行。
  • async:下载完立刻执行,顺序不保证,可能在 DOM 解析中途执行。

所以需要操作 DOM 的脚本用 defer,独立的第三方统计、广告脚本用 async。

注意:defer 和 async 只对外部脚本(有 src)有效,内联 <script> 写了也无效。

第七步:处理「返回页面」的场景

这一步解决一个隐蔽问题:用户点浏览器后退,页面从往返缓存(bfcache)恢复时,DOMContentLoaded 和 load 都不会再触发,但页面确实“重新出现”了。如果页面依赖加载事件刷新状态,就会显示旧数据。

用 pageshow 兜底:

window.addEventListener('pageshow', function (event) {
  if (event.persisted) {
    // 从 bfcache 恢复,需要手动刷新数据
    refreshData();
  }
});

event.persisted === true 就表示这次是从缓存恢复的,不是真实加载。

小结

  • 操作 DOM 用 document.addEventListener('DOMContentLoaded', fn),别用 window.onload,后者要等所有图片加载完,慢。
  • 需要读取图片尺寸、iframe 内容时,才用 window.addEventListener('load', fn)。
  • 写公共库时先判断 document.readyState,避免事件已触发导致回调不执行。
  • document.readyState 的 loading / interactive / complete 三个值,用来判断脚本运行阶段。
  • 新项目优先给 <script> 加 defer,让脚本天然在 DOM 就绪后执行,少一层缩进。
  • 页面从 bfcache 恢复时用 pageshow 的 event.persisted 判断并刷新数据。
  • jQuery 项目用 $(function(){});升级到 3.x 后 $(window).load() 要改成 $(window).on('load', fn)。
本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2480.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~