JS44 页面生命周期

W3C指定了Page Lifecycle API,统一了网页从诞生到卸载的行为模式,并且定义了新的事件,允许开发者响应网页状态的各种转换。

新的API

生命周期

网页的生命周期被分为了六个阶段,每个时刻只可能处于其中一个阶段。

(1)Active 阶段

网页处于可见状态,且拥有输入焦点。

(2)Passive阶段

此阶段网页可见,但是没有输入焦点,无法接受输入。UI更新(比如动画)仍然在执行。

(3)Hidden阶段

此阶段用户的桌面被其他窗口占据,网页不可见,当尚未冻结。UI更新不再执行。

(4)Terminated阶段

由于用户主动关闭窗口或者在同一个窗口但前往其他页面,导致当前页面被浏览器卸载并从内存中清除,页面进入此阶段。

这个阶段总是在Hidden阶段之后发生,此阶段不会自动任何新任务,正在进行的任务可能会被终止。

(5)Frozen阶段

页面处于Hidden阶段时间过长,但网页一直未关闭,页面会被冻结,进入Frozen阶段。也有可能处于可见状态的页面长时间没有操作,也会进入Frozen阶段。

这个阶段,网页不会再被分配CPU资源,定时器、回调函数、网络请求、DOM操作都不会执行,但是正在执行的任务会被执行完。浏览器可能会允许Frozen阶段的页面,周期性复苏一小段时间,短暂变回Hidden阶段,允许一小部分任务执行。

(6)Discarded阶段

网页长时间处于Frozen阶段,用户未唤醒页面,就会进入Discarded阶段。此阶段浏览器自动卸载网页,清楚网页内存占用。Passive阶段的网页如果长时间没有互动,也可能直接进入Discarded阶段。

这个阶段一般是系统强制执行,任何新任务和JavaScript代码都不能执行。

网页Discarded周,Tab窗口还在,如果用户重新访问Tab,浏览器会重新向服务器发出请求,再次加载网页,回到Active阶段

常见场景

(1)用户打开网页,切换到其他App,过了一会又重新回到网页

1
Active → Hidden → Active

(2)用户打开网页,切换到其他App,并且长时间使用后者,导致系统自动丢弃网页

1
Active → Hidden → Frozen → Discarded

(3)用户打开网页,切换到其他App,然后在任务管理器中将浏览器进程清楚。

1
Active → Hidden → Terminated

(4)系统丢弃了某个Tab里面的页面后,用户重新打开这个Tab

1
Discarded → Active

事件

网页的生命周期是在所有帧触发,不管是底层的帧,还是内嵌的帧。也就是说,内嵌的<iframe>网页跟顶层的网页一样,都会同时监听到所有的生命周期事件。

focus事件

focus事件在页面获得输入焦点时触发,比如网页从Passive阶段变为Active阶段。

1
window.addEventListener('focus', () => console.log('focus'))

blur事件

blur事件在页面失去输入焦点时触发,比如页面从Active阶段变为Passive阶段。

1
window.addEventListener('blur', (e) => console.log('blur'))

visibilitychange事件

visibilitychange事件在网页可见状态发生变化时触发,一般发生在下面的场景:

  1. 用户隐藏页面(切换Tab,最小化浏览器),页面由Active阶段变为Hidden阶段
  2. 用户重新访问隐藏的页面,页面由Hidden阶段进入Active阶段
  3. 用户关闭页面,页面先进入Hidden阶段,然后进入Terminated阶段。

Page Visibility API在document对象上新增了document.visibilityState属性,该属性返回一个字符串,表示页面当前的可见性状态,有三个可能的值:

  • hidden,页面彻底不可见
  • visibile,页面至少一部分课件
  • prerender,页面即将或正在渲染,处于不可见状态

通过监听visibilitychange事件,判断document.visibilityState属性,就可以获得页面的可见状态。

1
2
3
4
window.addEventListener('visibilitychange', (e) => console.log(document.visibilityState));

// 或者
document.addEventListener('visibilitychange', (e) => console.log(document.visibilityState));

由于历史兼容性的原因,document上定义了hidden属性,这是一个只读的属性,返回一个布尔值,表示当前页面是否可见。应当尽量避免使用这个属性。

freeze

freeze事件在网页进入Frozen阶段时触发,这个事件的监听函数,最长只能运行500毫秒,并且只能复用已经打开的网络连接,不能发起新的网络请求

1
2
3
4
document.addEventListener('freeze', (e) => console.log('freeze'))

// 或者
document.onfreeze = function(){}

注意,从Frozen阶段进入Discarded阶段,不会触发任何事件,无法指定回调函数,只能在进入Frozen阶段时指定。

resume事件

resume事件在网页离开Frozen阶段,变为Active/Passive/Hidden阶段时触发

1
2
3
4
document.addEventListener('resume', (e) => console.log('resume'))

// 或者
document.resume = function(){}

pageshow事件

pageshow事件在用户加载网页时触发,有可能是全新的页面加载,也有可能是从缓存中获取的页面。如果是从缓存中获取的页面,则该事件对象的event.persistedtrue,否则为false

1
window.addEventListener('pageshow', () => console.log('pageshow'));

这个事件与页面的可见性无关,至于浏览器的History记录变化有关。

pagehide事件

pagehide事件在用户离开网页、进入另一个网页时触发。它同样与页面的可见性没有关系,至于浏览器的History记录有关系。

1
window.addEventListener('pagehide', () => console.log('pagehide'));

如果浏览器能够将当前页面添加到缓存以供稍后重用,那么事件对象的event.persisted属性为true,否则为false。如果为true时,页面添加到了缓存,则页面进入Frozen阶段,否则进入Terminated状态。

beforeunload事件

beforeunload事件在窗口或文档即将卸载时触发,改时间发生时,文档依然可以见,此时卸载仍可取消。经过这个时间,网页进入Terminated状态。

1
2
3
window.addEventListener('beforeunload', function (event) {
event.returnValue = 'any content';
});

当为event.returnValue显式的设置任何值时,弹出确认窗口让用户自行选择是否关闭当前页面。没有赋值时,该事件不做任何响应。

从2011年5月25日起, HTML5规范声明:在该事件的处理函数中调用下列弹窗相关的方法时,可以忽略不执行,window.showModalDialog(), window.alert(), window.confirm()window.prompt()

要注意的是,beforeunload事件只应该用于提醒用户尚未保存的更改,如果这些更改已经保存,那么这个事件应该被移除。这个事件不应该无条件的加载到页面,因为这样做在某些情况下会造成性能的问题。

unload事件

unload事件在页面正在卸载时触发,经过这个事件,页面进入Terminated状态。

注意,不建议使用unload事件,因为这个时间是不可靠的,并且可能在某些情况下会有性能问题。

获取页面状态

如果页面处于Active、Passive、Hidden阶段,可以通过下面代码获取页面状态:

1
2
3
4
5
6
7
8
9
function getState() {
if (document.visibilityState === 'hidden') {
return 'hidden';
}
if (document.hasFocus()) {
return 'active';
}
return 'passive';
}

如果页面处于Frozen和Terminated阶段,由于定时器代码不会执行,只能通过事件监听判断状态,进入Frozen阶段,可以监听freeze时间,进入Terminated阶段,可以监听pageHide阶段

如果某个选项卡处于Frozen阶段,就随时有可能被系统丢弃,进入Terminated阶段。如果用户再次点击该选项卡,浏览器会重新加载该页面。可以通过判断document.wasDicarded属性,为true证明网页曾经被丢弃过。

如果页面被丢弃过,window对象上会新增window.clientIdwindow.discardedClientId两个属性,用来恢复丢弃前的状态

DOMContentLoaded事件和load事件

在页面开始加载时,会先后触发这两个事件。可以通过监听DOMContentLoaded事件和load事件来衡量页面的加载情况。

DOMContentLoaded事件表示DOM树构建完毕,可以安全的访问DOM树所有节点、绑定事件等(不需要等待图片、音视频等资源下载完成),jQuery的ready事件监听的就是这个事件)

load事件表示所有资源都已经加载完毕,图片、背景内容都完成渲染,页面出于可交互状态,jQuery的load事件监听的就是这个事件

参考