页面生命周期:DOMContentLoaded, load, beforeunload, unload

HTML页面的生命周期有以下三个重要事件:

  • DOMContentLoaded — 浏览器已经完全加载了HTMLDOM树已经构建完毕,但是像是 <img>和样式表等外部资源可能并没有下载完毕。
  • load — 浏览器已经加载了所有的资源(图像,样式表等)。
  • beforeunload/unload – 当用户离开页面的时候触发。

每个事件都有特定的用途

  • DOMContentLoadedDOM加载完毕,所以js可以访问所有DOM节点,初始化界面。
  • load – 附加资源已经加载完毕,可以在此事件触发时获得图像的大小(如果没有被在HTML/CSS中指定)
  • beforeunload/unload – 用户正在离开页面:可以询问用户是否保存了更改以及是否确定要离开页面。

来看一下每个事件的细节。

DOMContentLoaded

DOMContentLoadeddocument 对象触发。

我们使用 addEventListener 来监听它:

document.addEventListener("DOMContentLoaded", ready);

举个例子

<script>function ready() {alert('DOM is ready');// image is not yet loaded (unless was cached), so the size is 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">

在这个例子中 DOMContentLoadeddocument加载完成后就被触发,无需等待其他资源的载入,所以alert输出的图像的大小为0

这么看来DOMContentLoaded 似乎很简单,DOM树构建完毕之后就运行该事件,不过其实存在一些陷阱。

DOMContentLoaded 和脚本

当浏览器在解析HTML页面时遇到了 <script>...</script> 标签,将无法继续构建DOM树(译注:UI渲染线程与JS引擎是互斥的,当JS引擎执行时UI线程会被挂起),必须立即执行脚本。所以 DOMContentLoaded 有可能在所有脚本执行完毕后触发。

外部脚本(带src的)的加载和解析也会暂停DOM树构建,所以 DOMContentLoaded 也会等待外部脚本。

不过有两个例外是带asyncdefer的外部脚本,他们告诉浏览器继续解析而不需要等待脚本的执行,所以用户可以在脚本加载完成前可以看到页面,有较好的用户体验。

asyncdefer属性仅仅对外部脚本起作用,并且他们在src不存在时会被自动忽略。

它们都告诉浏览器继续处理页面上的内容,而在后台加载脚本,然后在脚本加载完毕后再执行。所以脚本不会阻塞DOM树的构建和页面的渲染。

(译注:其实这里是不对的,带有asyncdefer的脚本的下载是和HTML的下载与解析是异步的,但是js的执行一定是和UI线程是互斥的,像下面这张图所示,async在下载完毕后的执行会阻塞HTML的解析)
他们有两处不同:

async defer
顺序 带有async的脚本是优先执行先加载完的脚本,他们在页面中的顺序并不影响他们执行的顺序。 带有defer的脚本按照他们在页面中出现的顺序依次执行。
DOMContentLoaded 带有async的脚本也许会在页面没有完全下载完之前就加载,这种情况会在脚本很小或本缓存,并且页面很大的情况下发生。 带有defer的脚本会在页面加载和解析完毕后执行,刚好在DOMContentLoaded之前执行。

所以async用在那些完全不依赖其他脚本的脚本上。

DOMContentLoaded 与样式表

外部样式表并不会影响DOM,所以DOMContentLoaded并不会被他们阻塞。

不过仍然有一个陷阱:如果在样式后面有一个内联脚本,那么脚本必须等待样式先加载完。

(译注:简单来说,JS 因为有可能会去获取 DOM 的样式,所以 JS 会等待样式表加载完毕,而 JS 是阻塞 DOM 的解析的,所以在有外部样式表的时候,JS 会一直阻塞到外部样式表下载完毕)

<link type="text/css" rel="stylesheet" href="style.css">
<script>// the script doesn't not execute until the stylesheet is loaded// 脚本直到样式表加载完毕后才会执行。alert(getComputedStyle(document.body).marginTop);
</script>

发生这种事的原因是脚本也许会像上面的例子中所示,去得到一些元素的坐标或者基于样式的属性。所以他们自然要等到样式加载完毕才可以执行。

DOMContentLoaded需要等待脚本的执行,脚本又需要等待样式的加载。

浏览器的自动补全

Firefox, Chrome和Opera会在DOMContentLoaded执行时自动补全表单。

例如,如果页面有登录的界面,浏览器记住了该页面的用户名和密码,那么在 DOMContentLoaded运行的时候浏览器会试图自动补全表单(如果用户设置允许)。

所以如果DOMContentLoaded被一个需要长时间执行的脚本阻塞,那么自动补全也会等待。你也许见过某些网站(如果你的浏览器开启了自动补全)—— 浏览器并不会立刻补全登录项,而是等到整个页面加载完毕后才填充。这就是因为在等待DOMContentLoaded事件。

使用带asyncdefer的脚本的一个好处就是,他们不会阻塞DOMContentLoaded和浏览器自动补全。(译注:其实执行还是会阻塞的)

2018.02.05:defer 是会阻塞 DOMContentLoaded 的,被 defer 的脚本要在DOMContentLoaded 触发前执行,所以如果HTML很快就加载完了(先不考虑 CSS 阻塞 DOMContentLoaded 的情况),而 defer 的脚本还没有加载完,浏览器就会等,等到脚本加载完,执行完,再触发 DOMContentLoaded,放上一张图(取自在 devTool 下分析自己写的一个页面)
可以看到,HTML很快就加载和解析完毕(CSS 在这里是动态加载的,不阻塞 DOMContentLoaded),jQuerymain.js 的脚本是 defer 的, DOMContentLoaded(蓝线)一直在等,等到这两个脚本下载完并执行完,才触发了 DOMContentLoaded

从这个角度看来,defer 和把脚本放在 </body> 前真是没啥区别,只不过 defer 脚本位 于head 中,更早被读到,加载更早,而且不担心会被其他的脚本推迟下载开始的时间。

window.onload

window对象上的onload事件在所有文件包括样式表,图片和其他资源下载完毕后触发。

下面的例子正确检测了图片的大小,因为window.onload会等待所有图片的加载。

<script>window.onload = function() {alert('Page loaded');// image is loaded at this timealert(`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事件会被触发,我们可以做一些不存在延迟的事情,比如关闭弹出的窗口,可是我们无法阻止用户转移到另一个页面上。

所以我们需要使用另一个事件 — onbeforeunload

window.onbeforeunload

如果用户即将离开页面或者关闭窗口时,beforeunload事件将会被触发以进行额外的确认。

浏览器将显示返回的字符串,举个例子:

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

有些浏览器像Chrome和火狐会忽略返回的字符串取而代之显示浏览器自身的文本,这是为了安全考虑,来保证用户不受到错误信息的误导。

readyState

如果我们在整个页面加载完毕后设置DOMContentLoaded会发生什么呢?

啥也没有,DOMContentLoaded不会被触发。

有一些情况我们无法确定页面上是否已经加载完毕,比如一个带有async的外部脚本的加载和执行是异步的(注:执行并不是异步的-_-)。在不同的网络状况下,脚本有可能是在页面加载完毕后执行也有可能是在页面加载完毕前执行,我们无法确定。所以我们需要知道页面加载的状况。

document.readyState属性给了我们加载的信息,有三个可能的值:

  • loading 加载 - document仍在加载。
  • interactive 互动 - 文档已经完成加载,文档已被解析,但是诸如图像,样式表和框架之类的子资源仍在加载。
  • complete - 文档和所有子资源已完成加载。状态表示 load 事件即将被触发。

所以我们可以检查 document.readyState 的状态,如果没有就绪可以选择挂载事件,如果已经就绪了就可以直接立即执行。

像这样:

function work() { /*...*/ }if (document.readyState == 'loading') {document.addEventListener('DOMContentLoaded', work);
} else {work();
}

每当文档的加载状态改变的时候就有一个readystatechange事件被触发,所以我们可以打印所有的状态。

// current state
console.log(document.readyState);// print state changes
document.addEventListener('readystatechange', () => console.log(document.readyState));

readystatechange 是追踪页面加载的一个可选的方法,很早之前就已经出现了。不过现在很少被使用了,为了保持完整性还是介绍一下它。

readystatechange的在各个事件中的执行顺序又是如何呢?

<script>function log(text) { /* output the time and message */ }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>

输出如下:

    [1] initial readyState:loading[2] readyState:interactive[2] DOMContentLoaded[3] iframe onload[4] readyState:complete[4] img onload[4] window onload

方括号中的数字表示他们发生的时间,真实的发生时间会更晚一点,不过相同数字的时间可以认为是在同一时刻被按顺序触发(误差在几毫秒之内)

  • document.readyStateDOMContentLoaded前一刻变为interactive,这两个事件可以认为是同时发生。
  • document.readyState在所有资源加载完毕后(包括iframeimg)变成complete,我们可以看到completeimg.onloadwindow.onload几乎同时发生,区别就是window.onload在所有其他的load事件之后执行。

总结

页面事件的生命周期:

  • DOMContentLoaded事件在DOM树构建完毕后被触发,我们可以在这个阶段使用js去访问元素。

    • asyncdefer的脚本可能还没有执行。
    • 图片及其他资源文件可能还在下载中。
  • load事件在页面所有资源被加载完毕后触发,通常我们不会用到这个事件,因为我们不需要等那么久。
  • beforeunload在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展示并询问这个字符串以确定是否离开。
  • unload在用户已经离开时触发,我们在这个阶段仅可以做一些没有延迟的操作,由于种种限制,很少被使用。
  • document.readyState表征页面的加载状态,可以在readystatechange中追踪页面的变化状态:
    • loading — 页面正在加载中。
    • interactive – 页面解析完毕,时间上和 DOMContentLoaded同时发生,不过顺序在它之前。
    • complete – 页面上的资源都已加载完毕,时间上和window.onload同时发生,不过顺序在他之前。

HTML页面的生命周期相关推荐

  1. 【微信小程序企业级开发教程】页面的生命周期和参数传递

    文章目录 1 页面的生命周期 2 参数传递 2.1 第一种method 2.2 第二种method 1 页面的生命周期 2 参数传递 2.1 第一种method 第一个界面代码: 要跳转界面的代码: ...

  2. Asp.net2.0页面的生命周期(续)

    以上就是Asp.net页面生命周期中的几个主要事件.每次我们请求一个Asp.net页面时,我们都经历着同样的过程:从初始化对象到销毁对象.通过了解Asp.net页面的内部运行机制,我相信大家在编写.调 ...

  3. 【转】Asp.net页面的生命周期

    介绍 Asp.net是微软.Net战略的一个组成部分.它相对以前的Asp有了很大的发展,引入了许多的新机制.本文就Asp.net页面的生命周期向大家做一个初步的介绍,以期能起到指导大家更好.更灵活地操 ...

  4. .Net页面的生命周期(ZZ)

    1.        初始化:主要是执行Page的Init事件和OnIint方法. 2.        加载视图状态:主要是执行LoadViewState方法,就是从ViewState中获取上一次的状态 ...

  5. WEB页面的生命周期,DOMContentLoaded,load,beforeunload,unload

    简言 理解WEB页面的生命周期,文档加载事件及顺序对WEB开发有十分的重要意义.如果不理解,在元素未加载就提前操作元素,则得不到想要的结果.而如果页面完全加载完成后,再进行操作,则又会影响用户体验. ...

  6. ZT Web Control 开发系列(一) 页面的生命周期

    http://www.cnblogs.com/joeliu/category/143125.html Page是WebForm编程基本元素,它从TemplateControl派生,而TemplateC ...

  7. Asp.net2.0页面的生命周期

    当一个获取网页的请求(可能是通过用户提 交完成的,也可能是通过超链接完成的)被发送到Web服务器后,这个页面就会接着运行从创建到处理完成的一系列事件.在我们试图建立Asp.net页面的 时候,这个执行 ...

  8. HTML 页面的生命周期、HTML 事件

    From:https://blog.csdn.net/WuLex/article/details/101016936 1.页面生命周期 HTML页面的生命周期有以下三个重要事件,每个事件都有特定的用途 ...

  9. 微信小程序详解——小程序的生命周期和页面的生命周期

    我是一名安卓程序员,我们安卓中最明显的特征就是类具有生命周期.所以当开发小程序的时候,我自然而然的会先研究小程序的生命周期.在Android中是通过Application来管理安卓程序的生命周期,小程 ...

  10. uni-app 页面组件生命周期

    不论是app还是小程序,生命周期是非常重要的知识点. uni-app 支持如下生命周期函数: 页面的生命周期 onLoad 监听页面加载,其参数为上个页面传递的数据,参数类型为object(用于页面传 ...

最新文章

  1. “数学不好,干啥都不行!”资深程序员:别再瞎努力了!
  2. vue 2.6 中 slot 的新用法
  3. Dalvik/ART(ANDROID)中的多线程机制(4)
  4. 使用ViewContainerRef探索Angular DOM操作技术
  5. python数据类型及字符编码
  6. 一些好用的nginx第三方模块
  7. scanf 与 scanf_s
  8. mysql最简单的查看_查看Mysql版本号 (最简单的是status )
  9. Linux上,最常用的一批命令解析
  10. 初级产品经理的日常工作流程汇总
  11. 神经网络模型的工作原理,神经网络模型数据处理
  12. 一文彻底搞懂激光雷达原理!
  13. 各层电子数排布规则_核外电子的排布及其规律,亨利·莫塞莱和查尔斯·巴克拉最先发现电子层...
  14. 39 What Determines the Kind of Person You Are ?是什么决定了你是哪种内型的人 ?
  15. SpringBoot优缺点分析
  16. Python|制作汉堡的解题方法
  17. 【...】12306官网购买指定铺位的车票
  18. Docker部署若伊前后端分离项目到阿里云服务器
  19. SBUF, TI/RI, ES
  20. 为何国外的人都爱用电子邮箱?注册电子邮箱有哪些好处呢

热门文章

  1. 数据库连接中OleDbConnection的用法
  2. oledbconnection java_如何使用C#和OleDbConnection读取.xlsx和.xls文件?
  3. 什么样的男人才是女人眼中最帅的男人
  4. 星球企划书 | 从这个星球赚取你的第一桶金
  5. 桌面上 计算机 回收站不见了怎么办,电脑回收站不见了怎么办 四种方法教你快速解决问题【图文教程】...
  6. Glide之Target
  7. WIFI模块AT指令配置模块连接路由向公网发送数据(花生壳内网穿墙)
  8. 【学步者日记】C#反射中NonPublic和Instance需要一起使用
  9. 黑马博客实战项目中 Mongoose 错误-RangeError: Maximum call stack size exceeded,返回的文档过大导致模板引擎无法渲染的问题
  10. AI工程化,让人工智能回归现实