『 文章首发于GitHub Blog 』javascript
原文地址:http://javascript.info/onload-ondomcontentloadedcss
HTML页面的生命周期有如下三个重要事件:html
DOMContentLoaded
— 浏览器已经彻底加载了HTML,DOM树已经构建完毕,可是像是 <img>
和样式表等外部资源可能并无下载完毕。load
— 浏览器已经加载了全部的资源(图像,样式表等)。beforeunload/unload
-- 当用户离开页面的时候触发。每一个事件都有特定的用途java
DOMContentLoaded
-- DOM加载完毕,因此js能够访问全部DOM节点,初始化界面。load
-- 附加资源已经加载完毕,能够在此事件触发时得到图像的大小(若是没有被在HTML/CSS中指定)beforeunload/unload
-- 用户正在离开页面:能够询问用户是否保存了更改以及是否肯定要离开页面。来看一下每一个事件的细节。jquery
DOMContentLoaded
由 document
对象触发。git
咱们使用 addEventListener
来监听它:github
document.addEventListener("DOMContentLoaded", ready);
复制代码
举个例子浏览器
<script> function ready() { alert('DOM is ready'); // image is not yet loaded (unless was cached), so the size is 0x0 alert(`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
在document加载完成后就被触发,无需等待其余资源的载入,因此alert
输出的图像的大小为0。缓存
这么看来DOMContentLoaded
彷佛很简单,DOM树构建完毕以后就运行该事件,不过其实存在一些陷阱。安全
当浏览器在解析HTML页面时遇到了 <script>...</script>
标签,将没法继续构建DOM树(译注:UI渲染线程与JS引擎是互斥的,当JS引擎执行时UI线程会被挂起),必须当即执行脚本。因此 DOMContentLoaded
有可能在全部脚本执行完毕后触发。
外部脚本(带src
的)的加载和解析也会暂停DOM树构建,因此 DOMContentLoaded
也会等待外部脚本。
不过有两个例外是带async
和defer
的外部脚本,他们告诉浏览器继续解析而不须要等待脚本的执行,因此用户能够在脚本加载完成前能够看到页面,有较好的用户体验。
async
和defer
属性仅仅对外部脚本起做用,而且他们在src
不存在时会被自动忽略。
它们都告诉浏览器继续处理页面上的内容,而在后台加载脚本,而后在脚本加载完毕后再执行。因此脚本不会阻塞DOM树的构建和页面的渲染。
(译注:其实这里是不对的,带有async
和defer
的脚本的下载是和HTML的下载与解析是异步的,可是js的执行必定是和UI线程是互斥的,像下面这张图所示,async
在下载完毕后的执行会阻塞HTML的解析)
他们有两处不一样:
async |
defer |
|
---|---|---|
顺序 | 带有async 的脚本是优先执行先加载完的脚本,他们在页面中的顺序并不影响他们执行的顺序。 |
带有defer 的脚本按照他们在页面中出现的顺序依次执行。 |
DOMContentLoaded |
带有async 的脚本也许会在页面没有彻底下载完以前就加载,这种状况会在脚本很小或本缓存,而且页面很大的状况下发生。 |
带有defer 的脚本会在页面加载和解析完毕后执行,恰好在 DOMContentLoaded 以前执行。 |
因此async
用在那些彻底不依赖其余脚本的脚本上。
### DOMContentLoaded and styles
External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them.
外部样式表并不会影响DOM,因此`DOMContentLoaded`并不会被他们阻塞。
But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:
不过仍然有一个陷阱:若是在样式后面有一个内联脚本,那么脚本必须等待样式先加载完。
<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
事件。
使用带async
和defer
的脚本的一个好处就是,他们不会阻塞DOMContentLoaded
和浏览器自动补全。(译注:其实执行仍是会阻塞的)
2018.02.05:defer
是会阻塞DOMContentLoaded
的,被defer
的脚本要在DCL触发前执行,因此若是HTML很快就加载完了(先不考虑CSS阻塞DLC的状况),而defer
的脚本尚未加载完,浏览器就会等,等到脚本加载完,执行完,再触发DLC,放上一张图(取自在devTool下分析本身写的一个页面)
能够看到,HTML很快就加载和解析完毕(CSS在这里是动态加载的,不阻塞DLC),jquery和main.js的脚本是defer
的,DLC(蓝线)一直在等,等到这两个脚本下载完并执行完,才触发了DLC。 从这个角度看来,defer
和把脚本放在</body>
前真是没啥区别,只不过defer
脚本位于head
中,更早被读到,加载更早,并且不担忧会被其余的脚本推迟下载开始的时间。
window
对象上的onload
事件在全部文件包括样式表,图片和其余资源下载完毕后触发。
下面的例子正确检测了图片的大小,由于window.onload
会等待全部图片的加载。
<script> window.onload = function() { alert('Page loaded'); // image is loaded at this time 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
对象上的unload
事件会被触发,咱们能够作一些不存在延迟的事情,好比关闭弹出的窗口,但是咱们没法阻止用户转移到另外一个页面上。
因此咱们须要使用另外一个事件 — onbeforeunload
。
若是用户即将离开页面或者关闭窗口时,beforeunload
事件将会被触发以进行额外的确认。
浏览器将显示返回的字符串,举个例子:
window.onbeforeunload = function() {
return "There are unsaved changes. Leave now?";
};
复制代码
有些浏览器像Chrome和火狐会忽略返回的字符串取而代之显示浏览器自身的文本,这是为了安全考虑,来保证用户不受到错误信息的误导。
若是咱们在整个页面加载完毕后设置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>
复制代码
输出以下:
方括号中的数字表示他们发生的时间,真实的发生时间会更晚一点,不过相同数字的时间能够认为是在同一时刻被按顺序触发(偏差在几毫秒以内)
document.readyState
在 DOMContentLoaded
前一刻变为interactive
,这两个事件能够认为是同时发生。document.readyState
在全部资源加载完毕后(包括iframe
和img
)变成complete
,咱们能够看到complete
、 img.onload
和window.onload
几乎同时发生,区别就是window.onload
在全部其余的load
事件以后执行。页面事件的生命周期:
DOMContentLoaded
事件在DOM树构建完毕后被触发,咱们能够在这个阶段使用js去访问元素。
async
和defer
的脚本可能尚未执行。load
事件在页面全部资源被加载完毕后触发,一般咱们不会用到这个事件,由于咱们不须要等那么久。beforeunload
在用户即将离开页面时触发,它返回一个字符串,浏览器会向用户展现并询问这个字符串以肯定是否离开。unload
在用户已经离开时触发,咱们在这个阶段仅能够作一些没有延迟的操做,因为种种限制,不多被使用。document.readyState
表征页面的加载状态,能够在readystatechange
中追踪页面的变化状态:
loading
— 页面正在加载中。interactive
-- 页面解析完毕,时间上和 DOMContentLoaded
同时发生,不过顺序在它以前。complete
-- 页面上的资源都已加载完毕,时间上和window.onload
同时发生,不过顺序在他以前。