0%

页面生命周期

页面生命周期

在一个网页加载的过程中,有三个重要的阶段以及与之对应的事件:

  • DOMContentLoaded:HTML加载完成并构建了DOM树,但外部样式表资源或者图片资源等还尚未加载完成
  • load:加载了所有的外部资源
  • beforeunload/unload:用户将要离开页面时

每一种事件因其所处的特殊阶段,具有不同的意义:

  • DOMContentLoaded:DOM构建完成,处理函数可以查找DOM节点,初始化接口等
  • load:可以获取图片资源大小等(如果尚未在HTML/CSS中指定)
  • beforeunload/unload:询问用户是否保存了操作等

DOMContentLoaded

该事件发生在document对象上,需要用addEventListener来绑定监听函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<script>
<script>
function ready() {
alert('DOM is ready');

// 图片资源还未加载,除非已经缓存过,否则这里应当获取的结果是0*0
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时间看上去虽然很简单,但是也有一些值得注意的地方。

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
2
3
4
5
<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>

如果我们在link标签之后紧跟着一个script标签,那么DOMContentLoaded要被外部样式表的加载所阻塞,因为向上面的例子一样,script可能要获取依赖样式的信息,因为script会阻塞DOMContentLoaded,所以link这里也要去等待它执行完成。

浏览器的自动填充

你可能注意到,有时候浏览器的自动填充密码会有个小小的延迟,这是因为这种autofill是监听DOMContentLoaded事件导致的,因为脚本的加载可能阻塞该事件的触发,所以有时会有一个延迟。

这时也体现了async和defer的好处,我们通过设置async和defer,让脚本不阻塞该事件的触发,这样也能尽早地进行表单的自动填充。

load事件

window上的load事件,会在整个页面所有资源加载完毕后触发,如图片样式等。

1
2
3
4
5
6
7
8
9
10
<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">

这一次我们就能获取到正确的图片的长宽了。

unload事件

只在用户离开页面的时候触发,只能做一些不涉及延迟或者询问用户的事情,因为这个原因很少使用

beforeunload事件

在用户想要离开页面之前触发,如果处理函数返回一个字符串,会在用户离开时进行弹窗询问是否真的想离开。

readyState

有时候不确定页面是否已经成功加载,比如说设置了async和defer的外部脚本,因此我们需要确切的知道document当前的state。这是我们使用document的readyState属性,该属性可以是如下值:

  • loading:document正在加载
  • interactive:document已经读取完毕
  • complete:外部资源也已都加载完成

当state发生改变的时候,会触发readystatechange事件,这是个历史遗留产物,现在已很少使用。