页面生命周期
在一个网页加载的过程中,有三个重要的阶段以及与之对应的事件:
- DOMContentLoaded:HTML加载完成并构建了DOM树,但外部样式表资源或者图片资源等还尚未加载完成
- load:加载了所有的外部资源
- beforeunload/unload:用户将要离开页面时
每一种事件因其所处的特殊阶段,具有不同的意义:
- DOMContentLoaded:DOM构建完成,处理函数可以查找DOM节点,初始化接口等
- load:可以获取图片资源大小等(如果尚未在HTML/CSS中指定)
- beforeunload/unload:询问用户是否保存了操作等
DOMContentLoaded
该事件发生在document对象上,需要用addEventListener来绑定监听函数。
1 |
|
DOMContentLoaded时间看上去虽然很简单,但是也有一些值得注意的地方。
DOMContentLoaded和script的关系
当浏览器解析HTML的时候遇到一个script标签,就会暂停对DOM的解析,转而执行js脚本,只有这样的script标签中的脚本执行完毕才会触发DOMContentLoaded事件。
通过src属性引入的外部脚步也会阻塞DOM的解析,因而DOMContentLoaded也会等待这些外部脚本加载执行完毕。
唯一例外的是带有async和defer属性的外部脚本。
带有async和defer的外部脚本
async和defer只适用于外部脚本,如果script标签没有src属性,即便设置async和defer也会自动忽略。
这两个属性都告诉浏览器可以继续解析DOM,而在后台去加载和解析JS文件。
async和defer也有一些不同的特性:
首先是运行的顺序,对于async加载的脚本而言,遵循先加载的先运行原则,而defer加载的脚本严格按照document中定义的先后次序来执行。
然后是和DOMContentLoaded事件的关系,对于async加载的脚本,可能会在document还没有完全下载完成之前就执行了,适用于document很长,而脚本文件很小或者已经被缓存过的情况;而defer加载的脚本会等待document加载和解析完成才会执行,如果有必要还会等待,并且正好是在DOMContentLoaded事件发生之前去执行。
由于它们特性的不同,所适用的场景也不同:
- async适用于一个个的独立的脚本,比如计数器或者广告,它们不需要获取页面内容,并且相对执行序也没什么所谓
- defer适用于需要使用DOM的脚本并且相对执行序重要的场景
DOMContentLoaded和style的关系
外部样式表不影响DOM,所以按理说DOMContentLoaded也无需等待它们的加载,但是有一个陷阱。
1 | <link type="text/css" rel="stylesheet" href="style.css"> |
如果我们在link标签之后紧跟着一个script标签,那么DOMContentLoaded要被外部样式表的加载所阻塞,因为向上面的例子一样,script可能要获取依赖样式的信息,因为script会阻塞DOMContentLoaded,所以link这里也要去等待它执行完成。
浏览器的自动填充
你可能注意到,有时候浏览器的自动填充密码会有个小小的延迟,这是因为这种autofill是监听DOMContentLoaded事件导致的,因为脚本的加载可能阻塞该事件的触发,所以有时会有一个延迟。
这时也体现了async和defer的好处,我们通过设置async和defer,让脚本不阻塞该事件的触发,这样也能尽早地进行表单的自动填充。
load事件
window上的load事件,会在整个页面所有资源加载完毕后触发,如图片样式等。
1 | <script> |
这一次我们就能获取到正确的图片的长宽了。
unload事件
只在用户离开页面的时候触发,只能做一些不涉及延迟或者询问用户的事情,因为这个原因很少使用
beforeunload事件
在用户想要离开页面之前触发,如果处理函数返回一个字符串,会在用户离开时进行弹窗询问是否真的想离开。
readyState
有时候不确定页面是否已经成功加载,比如说设置了async和defer的外部脚本,因此我们需要确切的知道document当前的state。这是我们使用document的readyState属性,该属性可以是如下值:
- loading:document正在加载
- interactive:document已经读取完毕
- complete:外部资源也已都加载完成
当state发生改变的时候,会触发readystatechange事件,这是个历史遗留产物,现在已很少使用。