Android、iOS 和最新的 Windows 系统能够随时自主地中止后台进程,及时释放系统资源。也就是说,网页可能随时被系统丢弃掉。之前的浏览器 API 彻底没有考虑到这种状况,致使开发者根本没有办法监听到系统丢弃页面。javascript
为了解决这个问题,W3C 新制定了一个 Page Lifecycle API,统一了网页从诞生到卸载的行为模式,而且定义了新的事件,容许开发者响应网页状态的各类转换。java
有了这个 API,开发者就能够预测网页下一步的状态,从而进行各类针对性的处理。Chrome 68 支持这个 API,对于老式浏览器可使用谷歌开发的兼容库 PageLifecycle.js。浏览器
网页的生命周期分红六个阶段,每一个时刻只可能处于其中一个阶段。缓存
(1)Active 阶段服务器
在 Active 阶段,网页处于可见状态,且拥有输入焦点。网络
(2)Passive 阶段ide
在 Passive 阶段,网页可见,但没有输入焦点,没法接受输入。UI 更新(好比动画)仍然在执行。该阶段只可能发生在桌面同时有多个窗口的状况。函数
(3)Hidden 阶段动画
在 Hidden 阶段,用户的桌面被其余窗口占据,网页不可见,但还没有冻结。UI 更新再也不执行。code
(4)Terminated 阶段
在 Terminated 阶段,因为用户主动关闭窗口,或者在同一个窗口前往其余页面,致使当前页面开始被浏览器卸载并从内存中清除。注意,这个阶段老是在 Hidden 阶段以后发生,也就是说,用户主动离开当前页面,老是先进入 Hidden 阶段,再进入 Terminated 阶段。
这个阶段会致使网页卸载,任何新任务都不会在这个阶段启动,而且若是运行时间太长,正在进行的任务可能会被终止。
(5)Frozen 阶段
若是网页处于 Hidden 阶段的时间太久,用户又不关闭网页,浏览器就有可能冻结网页,使其进入 Frozen 阶段。不过,也有可能,处于可见状态的页面长时间没有操做,也会进入 Frozen 阶段。
这个阶段的特征是,网页不会再被分配 CPU 计算资源。定时器、回调函数、网络请求、DOM 操做都不会执行,不过正在运行的任务会执行完。浏览器可能会容许 Frozen 阶段的页面,周期性复苏一小段时间,短暂变回 Hidden 状态,容许一小部分任务执行。
(6)Discarded 阶段
若是网页长时间处于 Frozen 阶段,用户又不唤醒页面,那么就会进入 Discarded 阶段,即浏览器自动卸载网页,清除该网页的内存占用。不过,Passive 阶段的网页若是长时间没有互动,也可能直接进入 Discarded 阶段。
这通常是在用户没有介入的状况下,由系统强制执行。任何类型的新任务或 JavaScript 代码,都不能在此阶段执行,由于这时一般处在资源限制的情况下。
网页被浏览器自动 Discarded 之后,它的 Tab 窗口仍是在的。若是用户从新访问这个 Tab 页,浏览器将会从新向服务器发出请求,再一次从新加载网页,回到 Active 阶段。
如下是几个常见场景的网页生命周期变化。
(1)用户打开网页后,又切换到其余 App,但只过了一会又回到网页。
网页由 Active 变成 Hidden,又变回 Active。
(2)用户打开网页后,又切换到其余 App,而且长时候使用后者,致使系统自动丢弃网页。
网页由 Active 变成 Hidden,再变成 Frozen,最后 Discarded。
(3)用户打开网页后,又切换到其余 App,而后从任务管理器里面将浏览器进程清除。
网页由 Active 变成 Hidden,而后 Terminated。
(4)系统丢弃了某个 Tab 里面的页面后,用户从新打开这个 Tab。
网页由 Discarded 变成 Active。
生命周期的各个阶段都有本身的事件,以供开发者指定监听函数。这些事件里面,只有两个是新定义的(freeze
事件和resume
事件),其它都是现有的。
注意,网页的生命周期事件是在全部帧(frame)触发,不论是底层的帧,仍是内嵌的帧。也就是说,内嵌的<iframe>
网页跟顶层网页同样,都会同时监听到下面的事件。
focus
事件在页面得到输入焦点时触发,好比网页从 Passive 阶段变为 Active 阶段。
blur
事件在页面失去输入焦点时触发,好比网页从 Active 阶段变为 Passive 阶段。
visibilitychange
事件在网页可见状态发生变化时触发,通常发生在如下几种场景。
- 用户隐藏页面(切换 Tab、最小化浏览器),页面由 Active 阶段变成 Hidden 阶段。
- 用户从新访问隐藏的页面,页面由 Hidden 阶段变成 Active 阶段。
- 用户关闭页面,页面会先进入 Hidden 阶段,而后进入 Terminated 阶段。
能够经过document.onvisibilitychange
属性指定这个事件的回调函数。
freeze
事件在网页进入 Frozen 阶段时触发。
能够经过document.onfreeze
属性指定在进入 Frozen 阶段时调用的回调函数。
function handleFreeze(e) { // Handle transition to FROZEN } document.addEventListener('freeze', handleFreeze); # 或者 document.onfreeze = function() { … }
这个事件的监听函数,最长只能运行500毫秒。而且只能复用已经打开的网络链接,不能发起新的网络请求。
注意,从 Frozen 阶段进入 Discarded 阶段,不会触发任何事件,没法指定回调函数,只能在进入 Frozen 阶段时指定回调函数。
resume
事件在网页离开 Frozen 阶段,变为 Active / Passive / Hidden 阶段时触发。
document.onresume
属性指的是页面离开 Frozen 阶段、进入可用状态时调用的回调函数。
function handleResume(e) { // handle state transition FROZEN -> ACTIVE } document.addEventListener("resume", handleResume); # 或者 document.onresume = function() { … }
pageshow
事件在用户加载网页时触发。这时,有多是全新的页面加载,也多是从缓存中获取的页面。若是是从缓存中获取,则该事件对象的event.persisted
属性为true
,不然为false
。
这个事件的名字有点误导,它跟页面的可见性其实毫无关系,只跟浏览器的 History 记录的变化有关。
pagehide
事件在用户离开当前网页、进入另外一个网页时触发。它的前提是浏览器的 History 记录必须发生变化,跟网页是否可见无关。
若是浏览器可以将当前页面添加到缓存以供稍后重用,则事件对象的event.persisted
属性为true
。 若是为true
。若是页面添加到了缓存,则页面进入 Frozen 状态,不然进入 Terminatied 状态。
beforeunload
事件在窗口或文档即将卸载时触发。该事件发生时,文档仍然可见,此时卸载仍可取消。通过这个事件,网页进入 Terminated 状态。
unload
事件在页面正在卸载时触发。通过这个事件,网页进入 Terminated 状态。
若是网页处于 Active、Passive 或 Hidden 阶段,能够经过下面的代码,得到网页当前的状态。
const getState = () => { if (document.visibilityState === 'hidden') { return 'hidden'; } if (document.hasFocus()) { return 'active'; } return 'passive'; };
若是网页处于 Frozen 和 Terminated 状态,因为定时器代码不会执行,只能经过事件监听判断状态。进入 Frozen 阶段,能够监听freeze
事件;进入 Terminated 阶段,能够监听pagehide
事件。
若是某个选项卡处于 Frozen 阶段,就随时有可能被系统丢弃,进入 Discarded 阶段。若是后来用户再次点击该选项卡,浏览器会从新加载该页面。
这时,开发者能够经过判断document.wasDiscarded
属性,了解先前的网页是否被丢弃了。
if (document.wasDiscarded) { // 该网页已经不是原来的状态了,曾经被浏览器丢弃过 // 恢复之前的状态 getPersistedState(self.discardedClientId); }
同时,window
对象上会新增window.clientId
和window.discardedClientId
两个属性,用来恢复丢弃前的状态。