DOMContentLoaded,load,beforeunload,unload

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

  • DOMContentLoaded —— 浏览器已完全加载 HTML,并构建了 DOM 树,但像 <img> 和样式表之类的外部资源可能尚未加载完成。

  • load —— 浏览器不仅加载完成了 HTML,还加载完成了所有外部资源:图片,样式等。

  • beforeunload/unload —— 当用户正在离开页面时。

每个事件都是有用的:

  • DOMContentLoaded 事件 —— DOM 已经就绪,因此处理程序可以查找 DOM 节点,并初始化接口。

  • load 事件 —— 外部资源已加载完成,样式已被应用,图片大小也已知了。

  • beforeunload 事件 —— 用户正在离开:我们可以检查用户是否保存了更改,并询问他是否真的要离开。

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

我们探索一下这些事件的细节。

DOMContentLoaded

DOMContentLoaded 事件发生在 document 对象上。

我们必须使用 addEventListener 来捕获它:

document.addEventListener("DOMContentLoaded", ready);
// 不是 "document.onDOMContentLoaded = ..."

例如:

<script>function ready() {alert('DOM is ready');// 图片目前尚未加载完成(除非已经被缓存),所以图片的大小为 0x0alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);}document.addEventListener("DOMContentLoaded", ready);
</script><img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

在示例中,DOMContentLoaded 处理程序在文档加载完成后触发,所以它可以查看所有元素,包括它下面的 <img> 元素。

但是,它不会等待图片加载。因此,alert 显示其大小为零。

乍一看,DOMContentLoaded 事件非常简单。DOM 树准备就绪 —— 这是它的触发条件。它并没有什么特别之处。

DOMContentLoaded 和脚本

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

因此,DOMContentLoaded 肯定在下面的这些脚本执行结束之后发生:

<script>document.addEventListener("DOMContentLoaded", () => {alert("DOM ready!");});
</script><script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"></script><script>alert("Library loaded, inline script executed");
</script>

在上面这个例子中,我们首先会看到 "Library loaded...",然后才会看到 "DOM ready!"(所有脚本都已经执行结束)。

不会阻塞 DOMContentLoaded 的脚本:

此规则有两个例外:

  1. 具有 async 特性(attribute)的脚本不会阻塞 DOMContentLoaded,稍后[1] 我们会讲到。

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

DOMContentLoaded 和样式

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

但这里有一个陷阱。如果在样式后面有一个脚本,那么该脚本必须等待样式表加载完成:

<link type="text/css" rel="stylesheet" href="style.css">
<script>// 在样式表加载完成之前,脚本都不会执行alert(getComputedStyle(document.body).marginTop);
</script>

原因是,脚本可能想要获取元素的坐标和其他与样式相关的属性,如上例所示。因此,它必须等待样式加载完成。

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

浏览器内建的自动填充

Firefox,Chrome 和 Opera 都会在 DOMContentLoaded 中自动填充表单。

例如,如果页面有一个带有登录名和密码的表单,并且浏览器记住了这些值,那么在 DOMContentLoaded 上,浏览器会尝试自动填充它们(如果得到了用户允许)。

因此,如果 DOMContentLoaded 被需要加载很长时间的脚本延迟触发,那么自动填充也会等待。你可能在某些网站上看到过(如果你使用浏览器自动填充)—— 登录名/密码字段不会立即自动填充,而是在页面被完全加载前会延迟填充。这实际上是 DOMContentLoaded 事件之前的延迟。

window.onload

当整个页面,包括样式、图片和其他资源被加载完成时,会触发 window 对象上的 load 事件。可以通过 onload 属性获取此事件。

下面的这个示例正确显示了图片大小,因为 window.onload 会等待所有图片加载完毕:

<script>window.onload = function() { // 与此相同 window.addEventListener('load', (event) => {alert('Page loaded');// 此时图片已经加载完成alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);};
</script><img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">

window.onunload

当访问者离开页面时,window 对象上的 unload 事件就会被触发。我们可以在那里做一些不涉及延迟的操作,例如关闭相关的弹出窗口。

有一个值得注意的特殊情况是发送分析数据。

假设我们收集有关页面使用情况的数据:鼠标点击,滚动,被查看的页面区域等。

自然地,当用户要离开的时候,我们希望通过 unload 事件将数据保存到我们的服务器上。

有一个特殊的 navigator.sendBeacon(url, data) 方法可以满足这种需求,详见规范 https://w3c.github.io/beacon/。

它在后台发送数据,转换到另外一个页面不会有延迟:浏览器离开页面,但仍然在执行 sendBeacon

使用方式如下:

let analyticsData = { /* 带有收集的数据的对象 */ };window.addEventListener("unload", function() {navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
});
  • 请求以 POST 方式发送。

  • 我们不仅能发送字符串,还能发送表单以及其他格式的数据,在 Fetch 一章有详细讲解,但通常它是一个字符串化的对象。

  • 数据大小限制在 64kb。

sendBeacon 请求完成时,浏览器可能已经离开了文档,所以就无法获取服务器响应(对于分析数据来说通常为空)。

还有一个 keep-alive 标志,该标志用于在 fetch[2] 方法中为通用的网络请求执行此类“离开页面后”的请求。你可以在 Fetch API[3] 一章中找到更多相关信息。

如果我们要取消跳转到另一页面的操作,在这里做不到。但是我们可以使用另一个事件 —— onbeforeunload

window.onbeforeunload

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

如果我们要取消事件,浏览器会询问用户是否确定。

你可以通过运行下面这段代码,然后重新加载页面来进行尝试:

window.onbeforeunload = function() {return false;
};

由于历史原因,返回非空字符串也被视为取消事件。在以前,浏览器曾经将其显示为消息,但是根据 现代规范[4] 所述,它们不应该这样。

这里有个例子:

window.onbeforeunload = function() {return "There are unsaved changes. Leave now?";
};

它的行为已经改变了,因为有些站长通过显示误导性和恶意信息滥用了此事件处理程序。所以,目前一些旧的浏览器可能仍将其显示为消息,但除此之外 —— 无法自定义显示给用户的消息。

readyState

如果我们将 DOMContentLoaded 事件处理程序设置在文档加载完成之后,会发生什么?

很自然地,它永远不会运行。

在某些情况下,我们不确定文档是否已经准备就绪。我们希望我们的函数在 DOM 加载完成时执行,无论现在还是以后。

document.readyState 属性可以为我们提供当前加载状态的信息。

它有 3 个可能值:

  • loading —— 文档正在被加载。

  • interactive —— 文档被全部读取。

  • complete —— 文档被全部读取,并且所有资源(例如图片等)都已加载完成。

所以,我们可以检查 document.readyState 并设置一个处理程序,或在代码准备就绪时立即执行它。

像这样:

function work() { /*...*/ }if (document.readyState == 'loading') {// 仍在加载,等待事件document.addEventListener('DOMContentLoaded', work);
} else {// DOM 已就绪!work();
}

还有一个 readystatechange 事件,会在状态发生改变时触发,因此我们可以打印所有这些状态,就像这样:

// 当前状态
console.log(document.readyState);// 状态改变时打印它
document.addEventListener('readystatechange', () => console.log(document.readyState));

readystatechange 事件是跟踪文档加载状态的另一种机制,它很早就存在了。现在则很少被使用。

但是为了完整起见,让我们看看完整的事件流。

这是一个带有 <iframe><img> 和记录事件的处理程序的文档:

<script>log('initial readyState:' + document.readyState);document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));window.onload = () => log('window onload');
</script><iframe src="iframe.html" onload="log('iframe onload')"></iframe><img src="http://en.js.cx/clipart/train.gif" id="img">
<script>img.onload = () => log('img onload');
</script>

此示例运行 在 sandbox 中[5]

典型输出:

  1. [1] initial readyState:loading

  2. [2] readyState:interactive

  3. [2] DOMContentLoaded

  4. [3] iframe onload

  5. [4] img onload

  6. [4] readyState:complete

  7. [4] window onload

方括号中的数字表示发生这种情况的大致时间。标有相同数字的事件几乎是同时发生的(+- 几毫秒)。

  • DOMContentLoaded 之前,document.readyState 会立即变成 interactive。它们俩的意义实际上是相同的。

  • 当所有资源(iframeimg)都加载完成后,document.readyState 变成 complete。这里我们可以发现,它与 img.onloadimg 是最后一个资源)和 window.onload 几乎同时发生。转换到 complete 状态的意义与 window.onload 相同。区别在于 window.onload 始终在所有其他 load 处理程序之后运行。

总结

页面生命周期事件:

  • 当 DOM 准备就绪时,document 上的 DOMContentLoaded 事件就会被触发。在这个阶段,我们可以将 JavaScript 应用于元素。

    • 诸如 <script>...</script><script src="..."></script> 之类的脚本会阻塞  DOMContentLoaded,浏览器将等待它们执行结束。

    • 图片和其他资源仍然可以继续被加载。

  • 当页面和所有资源都加载完成时,window 上的 load 事件就会被触发。我们很少使用它,因为通常无需等待那么长时间。

  • 当用户想要离开页面时,window 上的 beforeunload 事件就会被触发。如果我们取消这个事件,浏览器就会询问我们是否真的要离开(例如,我们有未保存的更改)。

  • 当用户最终离开时,window 上的 unload 事件就会被触发。在处理程序中,我们只能执行不涉及延迟或询问用户的简单操作。正是由于这个限制,它很少被使用。我们可以使用 navigator.sendBeacon 来发送网络请求。

  • document.readyState 是文档的当前状态,可以在 readystatechange 事件中跟踪状态更改:

    • loading —— 文档正在被加载。

    • interactive —— 文档已被解析完成,与 DOMContentLoaded 几乎同时发生,但是在 DOMContentLoaded 之前发生。

    • complete —— 文档和资源均已加载完成,与 window.onload 几乎同时发生,但是在 window.onload 之前发生。


现代 JavaScript 教程:开源的现代 JavaScript 从入门到进阶的优质教程。React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程[6]

在线免费阅读:https://zh.javascript.info


参考资料

[1]

稍后: https://zh.javascript.info/script-async-defer

[2]

fetch: https://zh.javascript.info/fetch

[3]

Fetch API: https://zh.javascript.info/fetch-api

[4]

现代规范: https://html.spec.whatwg.org/#unloading-documents

[5]

在 sandbox 中: https://plnkr.co/edit/ct5SNvrHCA75b2KZ?p=preview

[6]

React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources

推荐阅读

我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?
若川知乎高赞:有哪些必看的 JS库?

末尾

你好,我是若川,江湖人称菜如若川,历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以点个在看呀^_^另外欢迎留言交流~

精选前端好文,伴你不断成长

若川原创文章精选!可点击

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

用框架的你,可能早已忽略了这些事件API相关推荐

  1. Nodejs爬虫刷csdn访客+桌面自动化框架Robotjs模拟鼠标,键盘,屏幕事件!

    叮铃铃!叮铃铃!老师:"小明 你的梦想是什么?", 沉思了一下 小明:"额额  我想有车有房,自己当老板,媳妇貌美如花,还有一个当官的兄弟" 老师:" ...

  2. 【Android 插件化】Hook 插件化框架 ( Hook 实现思路 | Hook 按钮点击事件 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  3. Microsoft.Net框架程序设计学习笔记(15):事件之原理分析

    完整定义一个事件的方法 以Mail发送为例: 定义一个类型用于保存所有需要发送给事件通知接受者的附加信息. 按.Net框架约定,所有保存事件信息的类型都应该继承自System.EventArgs,且类 ...

  4. 孢子java_孢子框架-接口访问层、ESB、微服务API GateWay对比

    标签: 如果从百度去搜索"接口访问层"你会发现主要是.NET里面的技术,叫做IDAL,其实是数据访问层接口.它的主要作用是兼容多种数据库.比如你定义一个标准接口,然后实现改接口的S ...

  5. linux流行开源监控框架,Inotify: 高效、实时的Linux文件系统事件监控框架

    概要 - 为什么需要监控文件系统? 在日常工作中,人们往往需要知道在某些文件(夹)上都有那些变化,比如: 通知配置文件的改变 跟踪某些关键的系统文件的变化 监控某个分区磁盘的整体使用情况 系统崩溃时进 ...

  6. Python【算法中心 02】Web框架Django管理页面使用(管理员账号创建+API使用+应用添加)GreenPlum数据库引擎及API测试

    1.SQLite管理员账号创建 SQLite 是 Django 默认的数据库体量上类似与 Apache Derby,配置信息如下: DATABASES = {'default': {'ENGINE': ...

  7. 大数据-NoSQL数据库-HBase操作框架:Phoenix【Java写的基于JDBC API的操作HBase数据库的SQL引擎框架;低延迟、事务性、可使用sql语句、提供JDBC接口】

    一.Phoenix概述 1.Phoenix 定义 Phoenix 最早是 saleforce 的一个开源项目,后来成为 Apache 的顶级项目. Phoenix 构建在 HBase 之上的开源 SQ ...

  8. 前端实现带二维码的工厂物料标签,发票生成以及打印(vue框架),以及监听浏览器打印事件的钩子函数

    首先用一个变量来存放后台传过来的票据的数据,我们就用allPrintData来存放,然后在模板中,创建一个新的dom节点 <div id='printArea'></div> ...

  9. 数据持久化框架为什么放弃Hibernate、JPA、Mybatis,最终选择JDBCTemplate!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框 ...

最新文章

  1. Javascript 中的神器——Promise
  2. 嗅探软件和网络测试,决战无线网络 七款浏览器载入速度测试
  3. 11.MapReduce第1部分
  4. 如何提高QnA maker机器人训练中文语义理解的能力
  5. c 语言差错编码实验结果,C语言程序设计实验报告(四).doc11111111111111111.doc
  6. 机器学习之分类性能度量指标 : ROC曲线、AUC值、正确率、召回率
  7. myPassword
  8. hibernate中查询方式(二):常用查询
  9. 无法安装visual studio code,snap vscode has install-snap change in process
  10. CyanogenMod12编译教程
  11. 清华大学镜像站miniconda下载地址
  12. css 字体特效代码,css实用字体特效
  13. 解决打开Chrome网页被2345篡改
  14. luogu P4299 首都
  15. opencv+face_recognition+python实现换脸(face swap)操作(3)——基于普氏分析法(Procrustes Analysis)的代码实现
  16. Selenium系列(二)对浏览器的常用操作
  17. Ubuntu安装wordpress
  18. 融资预审复核 代码梳理
  19. 零信任-深信服零信任aTrust介绍(5)
  20. URAL 提交_天涯浪子_新浪博客

热门文章

  1. mysql 主从优点_MySql主从配置实践及其优势浅谈
  2. vc mysql_vc6.0连接mysql数据库
  3. javascript是一门多线程的语言_Javascript:10天设计一门语言
  4. 中文格式_常见中文编码格式
  5. Robot Framework-Ride界面介绍及库的添加
  6. 矩阵学习摘记,欢迎指正
  7. github和gitlab仓库一起使用
  8. WPF 窗体边框处理
  9. hashmap中的hash扰动函数
  10. 算法题11 字符串的所有对称子串