页面生命周期事件

理解 DOMContentLoaded、load、beforeunload、unload 四个页面生命周期事件

问题

说说你对以下几个页面生命周期事件的理解:DOMContentLoaded,load,beforeunload,unload

解答

HTML 页面的生命周期包含三个重要事件:

DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img> 和样式表之类的外部资源可能尚未加载完成。DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。

load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。外部资源已加载完成,样式已被应用,图片大小也已知了。

beforeunload —— 用户正在离开页面时触发。我们可以检查用户是否保存了更改,并询问他是否真的要离开。

unload —— 用户几乎已经离开了,但是我们仍然可以启动一些操作,例如发送统计数据。

DOMContentLoaded 和脚本

当浏览器处理一个 HTML 文档,并在文档中遇到 <script> 标签时,就会在继续构建 DOM 之前运行它。这是一种防范措施,因为脚本可能想要修改 DOM,甚至对其执行 document.write 操作,所以 DOMContentLoaded 必须等待脚本执行结束。

document.addEventListener('DOMContentLoaded', () => {
  // DOM 已就绪,可以操作 DOM 节点
  console.log('DOM ready');
});

此规则有两个例外:

  • 具有 async 特性的脚本不会阻塞 DOMContentLoaded
  • 使用 document.createElement('script') 动态生成并添加到网页的脚本也不会阻塞 DOMContentLoaded

DOMContentLoaded 和样式

外部样式表不会影响 DOM,因此 DOMContentLoaded 不会等待它们。

但有一个陷阱:如果在样式后面有一个脚本,那么该脚本必须等待样式表加载完成。原因是,脚本可能想要获取元素的坐标和其他与样式相关的属性。因此,它必须等待样式加载完成。

当 DOMContentLoaded 等待脚本时,它现在也在等待脚本前面的样式。

window.onload

当整个页面,包括样式、图片和其他资源被加载完成时,会触发 window 对象上的 load 事件。

window.addEventListener('load', () => {
  // 页面和所有资源都已加载完成
  console.log('Page fully loaded');
});

window.onbeforeunload

如果访问者触发了离开页面的导航或试图关闭窗口,beforeunload 处理程序将要求进行更多确认。

window.addEventListener('beforeunload', (event) => {
  // 取消事件,浏览器会询问用户是否确定离开
  event.preventDefault();
  event.returnValue = '';
});

window.onunload

当访问者离开页面时,window 对象上的 unload 事件就会被触发。我们可以在那里做一些不涉及延迟的操作,例如发送分析数据。

window.addEventListener('unload', () => {
  // 发送统计数据
  navigator.sendBeacon('/analytics', JSON.stringify({
    page: location.href,
    time: Date.now()
  }));
});

navigator.sendBeacon(url, data) 方法在后台发送数据,转换到另外一个页面不会有延迟。当 sendBeacon 请求完成时,浏览器可能已经离开了文档,所以就无法获取服务器响应。

关键点

  • DOMContentLoaded 在 DOM 树构建完成后触发,但外部资源可能未加载完成,普通脚本会阻塞此事件
  • load 事件在页面和所有资源(图片、样式等)都加载完成后触发
  • beforeunload 可以在用户离开前弹出确认对话框,常用于提醒保存未保存的更改
  • unload 事件中只能执行不涉及延迟的操作,使用 navigator.sendBeacon 发送数据不会阻塞页面跳转