Web API从零开始——网页生命周期管理

2023-05-06 05:24:00

 

这个部分单独拿出来说,完全是因为 Android、iOS 和最新的 Windows 系统可以随时自主地停止后台进程,及时释放系统资源;也就是说,网页可能随时被系统丢弃掉。

以前的浏览器 API 完全没有考虑到这种情况,导致开发者根本没有办法监听到系统丢弃页面——为了解决这个问题,W3C 新制定了一个 Page Lifecycle API,统一了网页从诞生到卸载的行为模式,并且定义了新的事件,允许开发者响应网页状态的各种转换。

有了这个 API,开发者就可以预测网页下一步的状态,从而进行各种针对性的处理;Chrome 68 开始支持这个 API,对于老式浏览器可以使用谷歌开发的兼容库 PageLifecycle.js。

这里就主要聊聊它和衍生的页面可视化管理 API。

1 Page Lifecycle API

网页的生命周期分成六个阶段,每个时刻只可能处于其中一个阶段,如下图:

(1)Active 阶段

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

(2)Passive 阶段

在 Passive 阶段,网页可见,但没有输入焦点,无法接受输入,UI 更新(比如动画)仍然在执行;该阶段只可能发生在桌面同时有多个窗口的情况。

(3)Hidden 阶段

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

(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.1 常见场景

以下是几个常见场景的网页生命周期变化:

(1)用户打开网页后,又切换到其他 App,但只过了一会又回到网页。

-> 网页由 Active 变成 Hidden,又变回 Active。

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

-> 网页由 Active 变成 Hidden,再变成 Frozen,最后 Discarded。

(3)用户打开网页后,又切换到其他 App,然后从任务管理器里面将浏览器进程清除。

-> 网页由 Active 变成 Hidden,然后 Terminated。

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

-> 网页由 Discarded 变成 Active。

1.2 事件

生命周期的各个阶段都有自己的事件,以供开发者指定监听函数,这些事件里面,只有两个是新定义的(freeze 事件和 resume 事件),其它都是现有的。

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

(1)focus 事件

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

(2)blur 事件

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

(3)visibilitychange 事件

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

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

可以通过 document.onvisibilitychange 属性指定这个事件的回调函数。

(4)freeze 事件

freeze 事件在网页进入 Frozen 阶段时触发。

可以通过 document.onfreeze 属性指定在进入 Frozen 阶段时调用的回调函数:

function handleFreeze(e) { // Handle transition to FROZEN } document.addEventListener(freeze, handleFreeze); // 或者 document.onfreeze = function() { }

这个事件的监听函数,最长只能运行 500 毫秒,并且只能复用已经打开的网络连接,不能发起新的网络请求。

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

(5)resume 事件

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

document.onresume 属性指的是页面离开 Frozen 阶段、进入可用状态时调用的回调函数:

function handleResume(e) { // handle state transition FROZEN -> ACTIVE } document.addEventListener("resume", handleResume); // 或者 document.onresume = function() { }

(6)pageshow 事件

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

这个事件的名字有点误导,它跟页面的可见性其实毫无关系,只跟浏览器的 History 记录的变化有关。

(7)pagehide 事件

pagehide 事件在用户离开当前网页、进入另一个网页时触发,它的前提是浏览器的 History 记录必须发生变化,跟网页是否可见无关。

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

(8)beforeunload 事件

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

(9)unload 事件

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

1.3 获取当前阶段

如果网页处于 Active、Passive 或 Hidden 阶段,可以通过下面的代码,获得网页当前的状态:

const getState = () => { if (document.visibilityState === hidden) { return hidden; } if (document.hasFocus()) { return active; } return passive; };

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

1.4 document.wasDiscarded

如果某个选项卡处于 Frozen 阶段,就随时有可能被系统丢弃,进入 Discarded 阶段,如果后来用户再次点击该选项卡,浏览器会重新加载该页面。

这时,开发者可以通过判断 document.wasDiscarded 属性,了解先前的网页是否被丢弃了:

if (document.wasDiscarded) { // 该网页已经不是原来的状态了,曾经被浏览器丢弃过 // 恢复以前的状态 getPersistedState(self.discardedClientId); }

同时,window 对象上会新增 window.clientId 和 window.discardedClientId 两个属性,用来恢复丢弃前的状态。

2 Page Visibility API

有时候,开发者需要知道,用户正在离开页面,常用的方法是监听下面三个事件:

pagehidebeforeunloadunload

但是,这些事件在手机上可能不会触发,页面就直接关闭了;因为手机系统可以将一个进程直接转入后台,然后杀死:

用户点击了一条系统通知,切换到另一个 App用户进入任务切换窗口,切换到另一个 App用户点击了 Home 按钮,切换回主屏幕操作系统自动切换到另一个 App(比如,收到一个电话)

上面这些情况,都会导致手机将浏览器进程切换到后台,然后为了节省资源,可能就会杀死浏览器进程。

以前,页面被系统切换,以及系统清除浏览器进程,是无法监听到的,开发者想要指定,任何一种页面卸载情况下都会执行的代码,也是无法做到的。

为了解决这个问题,就诞生了 Page Visibility API;不管手机或桌面电脑,所有情况下,这个 API 都会监听到页面的可见性发生变化。

这个新的 API 的意义在于,通过监听网页的可见性,可以预判网页的卸载,还可以用来节省资源,减缓电能的消耗。

比如,一旦用户不看网页,下面这些网页行为都是可以暂停的:

对服务器的轮询网页动画正在播放的音频或视频

2.1 document.visibilityState

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

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

其中,hidden 状态和 visible 状态是所有浏览器都必须支持的;prerender 状态只在支持“预渲染”的浏览器上才会出现,比如 Chrome 浏览器就有预渲染功能,可以在用户不可见的状态下,预先把页面渲染出来,等到用户要浏览的时候,直接展示渲染好的网页。

只要页面可见,哪怕只露出一个角,document.visibilityState 属性就返回 visible,只有以下四种情况,才会返回 hidden:

浏览器最小化浏览器没有最小化,但是当前页面切换成了背景页浏览器将要卸载(unload)页面操作系统触发锁屏屏幕

可以看到,上面四种场景涵盖了页面可能被卸载的所有情况,也就是说,页面卸载之前,document.visibilityState 属性一定会变成 hidden,事实上这也是设计这个 API 的主要目的。

另外,早期版本的 API,这个属性还有第四个值 unloaded,表示页面即将卸载,现在已经被废弃了。

注意,document.visibilityState 属性只针对顶层窗口,内嵌的 <iframe> 页面的 document.visibilityState 属性由顶层窗口决定,使用 CSS 属性隐藏 <iframe> 页面(比如display: none;),并不会影响内嵌页面的可见性。

2.2 document.hidden

由于历史原因,这个 API 还定义了 document.hidden 属性,该属性只读,返回一个布尔值,表示当前页面是否可见。

当 document.visibilityState 属性返回 visible 时,document.hidden 属性返回 false;其他情况下,都返回 true。

该属性只是出于历史原因而保留的,只要有可能,都应该使用 document.visibilityState 属性,而不是使用这个属性。

2.3 visibilitychange 事件

只要 document.visibilityState 属性发生变化,就会触发 visibilitychange 事件;因此,可以通过监听这个事件(通过 document.addEventListener() 方法或 document.onvisibilitychange 属性),跟踪页面可见性的变化:

document.addEventListener(visibilitychange, function () { // 用户离开了当前页面 if (document.visibilityState === hidden) { document.title = 页面不可见; } // 用户打开或回到页面 if (document.visibilityState === visible) { document.title = 页面可见; } });

上面代码是 Page Visibility API 的最基本用法,可以监听可见性变化。

下面是另一个例子,一旦页面不可见,就暂停视频播放:

const vidElem = document.getElementById(video-demo); document.addEventListener(visibilitychange, startStopVideo); function startStopVideo() { if (document.visibilityState === hidden) { vidElem.pause(); } else if (document.visibilityState === visible) { vidElem.play(); } }

2.4 页面卸载

下面专门讨论一下,如何正确监听页面卸载。

页面卸载可以分成三种情况:

页面可见时,用户关闭 Tab 页或浏览器窗口页面可见时,用户在当前窗口前往另一个页面页面不可见时,用户或系统关闭浏览器窗口

这三种情况,都会触发 visibilitychange 事件,前两种情况,该事件在用户离开页面时触发;最后一种情况,该事件在页面从可见状态变为不可见状态时触发。

由此可见,visibilitychange 事件比 pagehide、beforeunload、unload 事件更可靠,所有情况下都会触发(从 visible 变为 hidden),因此,可以只监听这个事件,运行页面卸载时需要运行的代码,不用监听后面那三个事件。

甚至可以这样说,unload 事件在任何情况下都不必监听,beforeunload 事件只有一种适用场景,就是用户修改了表单,没有提交就离开当前页面;另一方面,指定了这两个事件的监听函数,浏览器就不会缓存当前页面。


以上就是关于《Web API从零开始——网页生命周期管理》的全部内容,本文网址:https://www.7ca.cn/baike/22995.shtml,如对您有帮助可以分享给好友,谢谢。
标签:
声明

排行榜