|
5 j) j8 _4 v: W4 v
这个部分单独拿出来说,完全是因为 Android、iOS 和最新的 Windows 系统可以随时自主地停止后台进程,及时释放系统资源;也就是说,网页可能随时被系统丢弃掉以前的浏览器 API 完全没有考虑到这种情况,导致开发者根本没有办法监听到系统丢弃页面——为了解决这个问题,。
% k, B4 ~6 M4 |- x+ }2 X. b$ u W3C 新制定了一个 Page Lifecycle API,统一了网页从诞生到卸载的行为模式,并且定义了新的事件,允许开发者响应网页状态的各种转换有了这个 API,开发者就可以预测网页下一步的状态,从而进行各种针对性的处理;。 / V* d( w Q. @8 I* ]
Chrome 68 开始支持这个 API,对于老式浏览器可以使用谷歌开发的兼容库 PageLifecycle.js这里就主要聊聊它和衍生的页面可视化管理 API1 Page Lifecycle API网页的生命周期分成六个阶段,每个时刻只可能处于其中一个阶段,如下图:
8 S4 G; E% g V2 N1 \. {" } (1)Active 阶段在 Active 阶段,网页处于可见状态,且拥有输入焦点(2)Passive 阶段在 Passive 阶段,网页可见,但没有输入焦点,无法接受输入,UI 更新(比如动画)仍然在执行;该阶段只可能发生在桌面同时有多个窗口的情况。 0 p" {$ G+ r! K+ J" G
(3)Hidden 阶段在 Hidden 阶段,用户的桌面被其他窗口占据,网页不可见,但尚未冻结;UI 更新不再执行(4)Terminated 阶段在 Terminated 阶段,由于用户主动关闭窗口,或者在同一个窗口前往其他页面,导致当前页面开始被浏览器卸载并从内存中清除;注意,这个阶段总是在 。 % s4 B K% A) g, K
Hidden 阶段之后发生,也就是说,用户主动离开当前页面,总是先进入 Hidden 阶段,再进入 Terminated 阶段这个阶段会导致网页卸载,任何新任务都不会在这个阶段启动,并且如果运行时间太长,正在进行的任务可能会被终止。
/ R* z$ y7 m7 M( |# C, ~" [( s (5)Frozen 阶段如果网页处于 Hidden 阶段的时间过久,用户又不关闭网页,浏览器就有可能冻结网页,使其进入 Frozen 阶段;不过,也有可能,处于可见状态的页面长时间没有操作,也会进入 Frozen
) X6 |( s# p4 i4 S6 | [, r) E 阶段这个阶段的特征是,网页不会再被分配 CPU 计算资源:定时器、回调函数、网络请求、DOM 操作都不会执行,不过正在运行的任务会执行完浏览器可能会允许 Frozen 阶段的页面,周期性复苏一小段时间,短暂变回 。 * L5 L4 C% F: k/ B* v5 R
Hidden 状态,允许一小部分任务执行(6)Discarded 阶段如果网页长时间处于 Frozen 阶段,用户又不唤醒页面,那么就会进入 Discarded 阶段,即浏览器自动卸载网页,清除该网页的内存占用;不过,。 ' e% M: Y: z( `* n2 x( m
Passive 阶段的网页如果长时间没有互动,也可能直接进入 Discarded 阶段这一般是在用户没有介入的情况下,由系统强制执行,任何类型的新任务或 JavaScript 代码,都不能在此阶段执行,因为这时通常处在资源限制的状况下。
7 j" c' M6 ]% K 网页被浏览器自动 Discarded 以后,它的 Tab 窗口还是在的如果用户重新访问这个 Tab 页,浏览器将会重新向服务器发出请求,再一次重新加载网页,回到 Active 阶段1.1 常见场景以下是几个常见场景的网页生命周期变化: ; s S) n% m+ V1 A
(1)用户打开网页后,又切换到其他 App,但只过了一会又回到网页-> 网页由 Active 变成 Hidden,又变回 Active(2)用户打开网页后,又切换到其他 App,并且长时候使用后者,导致系统自动丢弃网页。 5 p$ H/ R L* o- Y
-> 网页由 Active 变成 Hidden,再变成 Frozen,最后 Discarded(3)用户打开网页后,又切换到其他 App,然后从任务管理器里面将浏览器进程清除-> 网页由 Active 。
* Q. ~+ K' O& z 变成 Hidden,然后 Terminated(4)系统丢弃了某个 Tab 里面的页面后,用户重新打开这个 Tab-> 网页由 Discarded 变成 Active1.2 事件生命周期的各个阶段都有自己的事件,以供开发者指定监听函数,这些事件里面,只有两个是新定义的(。 % J$ h! j% {6 `
freeze 事件和 resume 事件),其它都是现有的注意,网页的生命周期事件是在所有帧(frame)触发,不管是底层的帧,还是内嵌的帧;也就是说,内嵌的 网页跟顶层网页一样,都会同时监听到下面的事件:。 + m! m- f% r8 w# O) F1 }
(1)focus 事件focus 事件在页面获得输入焦点时触发,比如网页从 Passive 阶段变为 Active 阶段(2)blur 事件blur 事件在页面失去输入焦点时触发,比如网页从 Active 。
! Y& m7 b8 Y M2 T2 Q2 a9 C 阶段变为 Passive 阶段(3)visibilitychange 事件visibilitychange 事件在网页可见状态发生变化时触发,一般发生在以下几种场景:用户隐藏页面(切换 Tab、最小化浏览器),页面由 。
+ s3 Q2 ~- p# R% O Active 阶段变成 Hidden 阶段用户重新访问隐藏的页面,页面由 Hidden 阶段变成 Active 阶段用户关闭页面,页面会先进入 Hidden 阶段,然后进入 Terminated 阶段可以通过
! L# f q2 j6 x5 ]" @( } document.onvisibilitychange 属性指定这个事件的回调函数(4)freeze 事件freeze 事件在网页进入 Frozen 阶段时触发可以通过 document.onfreeze。 ) B9 }- t& L% c. Y
属性指定在进入 Frozen 阶段时调用的回调函数:functionhandleFreeze(e){// Handle transition to FROZEN! P) R3 V9 T. P9 ~# v0 q/ [0 w
}document.addEventListener
. d. R: P4 T8 v0 z( {3 g) P (freeze,handleFreeze);// 或者
/ J7 ?4 f/ F% L- m& a4 |" o# b document.onfreeze=function(){…}这个事件的监听函数,最长只能运行 500 毫秒,并且只能复用已经打开的网络连接,不能发起新的网络请求。 ( z$ I# S5 Z1 ~8 v# D7 D( c% m% I
注意,从 Frozen 阶段进入 Discarded 阶段,不会触发任何事件,无法指定回调函数,只能在进入 Frozen 阶段时指定回调函数(5)resume 事件resume 事件在网页离开 Frozen 。
8 g" G8 a6 h F( G! [( S! F 阶段,变为 Active / Passive / Hidden 阶段时触发document.onresume 属性指的是页面离开 Frozen 阶段、进入可用状态时调用的回调函数:functionhandleResume。 3 ?$ ?& L2 T% Y) j5 n( I
(e){// handle state transition FROZEN -> ACTIVE/ M$ n0 b+ C9 F) O' r- a0 p, t0 J
}document.addEventListener("resume",handleResume);// 或者
7 t$ `$ U% F4 ?1 m+ B0 K
7 r5 U% n% |' r- ` document.onresume=function(){…}(6)pageshow 事件pageshow 事件在用户加载网页时触发,这时,有可能是全新的页面加载,也可能是从缓存中获取的页面;如果是从缓存中获取,则该事件对象的 , d, ~, u6 R+ b# L
event.persisted 属性为 true,否则为 false这个事件的名字有点误导,它跟页面的可见性其实毫无关系,只跟浏览器的 History 记录的变化有关(7)pagehide 事件pagehide。
3 a; y, k2 R5 ^6 \ 事件在用户离开当前网页、进入另一个网页时触发,它的前提是浏览器的 History 记录必须发生变化,跟网页是否可见无关如果浏览器能够将当前页面添加到缓存以供稍后重用,则事件对象的 event.persisted。
, N# [( w6 n7 N7 | 属性为 true; 如果页面添加到了缓存,则页面进入 Frozen 状态,否则进入 Terminatied 状态(8)beforeunload 事件beforeunload 事件在窗口或文档即将卸载时触发,该事件发生时,文档仍然可见,此时卸载仍可取消;经过这个事件,网页进入 。 6 N# v5 l; R5 G( L7 s- \
Terminated 状态(9)unload 事件unload 事件在页面正在卸载时触发,经过这个事件,网页进入 Terminated 状态1.3 获取当前阶段如果网页处于 Active、Passive 。
2 G' |; [# @7 u) c, J 或 Hidden 阶段,可以通过下面的代码,获得网页当前的状态:constgetState=()=>{if(document.visibilityState===hidden){returnhidden $ A$ g1 j7 B z+ \" J
;}if(document.hasFocus()){returnactive;}returnpassive;};如果网页处于 Frozen 和 Terminated 状态,由于定时器代码不会执行,只能通过事件监听判断状态;进入
: u3 N4 H, b$ M3 A Frozen 阶段,可以监听freeze事件;进入 Terminated 阶段,可以监听 pagehide 事件1.4 document.wasDiscarded如果某个选项卡处于 Frozen 阶段,就随时有可能被系统丢弃,进入 。
6 B: S: b- i4 r Discarded 阶段,如果后来用户再次点击该选项卡,浏览器会重新加载该页面这时,开发者可以通过判断 document.wasDiscarded 属性,了解先前的网页是否被丢弃了:if(document。 7 r3 y4 {4 {" I6 @, q: g* [9 {
.wasDiscarded){// 该网页已经不是原来的状态了,曾经被浏览器丢弃过 g& M) l% h- A$ l
// 恢复以前的状态8 D. d+ w/ C; G( g4 [* p
getPersistedState(self.discardedClientId);}同时,window 对象上会新增 3 I4 T1 ?8 w8 V# Y
window.clientId 和 window.discardedClientId 两个属性,用来恢复丢弃前的状态2 Page Visibility API有时候,开发者需要知道,用户正在离开页面,常用的方法是监听下面三个事件:。
( t# }- p n' } pagehidebeforeunloadunload但是,这些事件在手机上可能不会触发,页面就直接关闭了;因为手机系统可以将一个进程直接转入后台,然后杀死:用户点击了一条系统通知,切换到另一个 App用户进入任务切换窗口,切换到另一个 7 c# ?' D5 F( y) O. c! f
App用户点击了 Home 按钮,切换回主屏幕操作系统自动切换到另一个 App(比如,收到一个电话)上面这些情况,都会导致手机将浏览器进程切换到后台,然后为了节省资源,可能就会杀死浏览器进程以前,页面被系统切换,以及系统清除浏览器进程,是无法监听到的,开发者想要指定,任何一种页面卸载情况下都会执行的代码,也是无法做到的。
2 o9 Q- M& |5 F, r7 x; j1 { w- w' O4 q 为了解决这个问题,就诞生了 Page Visibility API;不管手机或桌面电脑,所有情况下,这个 API 都会监听到页面的可见性发生变化这个新的 API 的意义在于,通过监听网页的可见性,可以预判网页的卸载,还可以用来节省资源,减缓电能的消耗。 / h3 @. k5 E* f4 j$ M4 \: k* U
比如,一旦用户不看网页,下面这些网页行为都是可以暂停的:对服务器的轮询网页动画正在播放的音频或视频2.1 document.visibilityState这个 API 主要在 document 对象上,新增了一个
3 U5 v" X S+ v document.visibilityState 属性,该属性返回一个字符串,表示页面当前的可见性状态,共有三个可能的值:hidden:页面彻底不可见visible:页面至少一部分可见prerender
% v+ n$ ~, S5 k& S& k9 B :页面即将或正在渲染,处于不可见状态其中,hidden 状态和 visible 状态是所有浏览器都必须支持的;prerender 状态只在支持“预渲染”的浏览器上才会出现,比如 Chrome 浏览器就有预渲染功能,可以在用户不可见的状态下,预先把页面渲染出来,等到用户要浏览的时候,直接展示渲染好的网页。 * z [( ~6 J- d
只要页面可见,哪怕只露出一个角,document.visibilityState 属性就返回 visible,只有以下四种情况,才会返回 hidden:浏览器最小化浏览器没有最小化,但是当前页面切换成了背景页 " K; W) L0 q+ t: t
浏览器将要卸载(unload)页面操作系统触发锁屏屏幕可以看到,上面四种场景涵盖了页面可能被卸载的所有情况,也就是说,页面卸载之前,document.visibilityState 属性一定会变成 hidden
( [6 N& d; B- R ,事实上这也是设计这个 API 的主要目的另外,早期版本的 API,这个属性还有第四个值 unloaded,表示页面即将卸载,现在已经被废弃了注意,document.visibilityState 属性只针对顶层窗口,内嵌的
! `( o5 w0 B1 ~ 页面的 document.visibilityState 属性由顶层窗口决定,使用 CSS 属性隐藏 页面(比如display: none;),并不会影响内嵌页面的可见性。
/ W6 e+ y! b. m 2.2 document.hidden由于历史原因,这个 API 还定义了 document.hidden 属性,该属性只读,返回一个布尔值,表示当前页面是否可见当 document.visibilityState。 ( l% D, s1 }+ }- g6 U
属性返回 visible 时,document.hidden 属性返回 false;其他情况下,都返回 true该属性只是出于历史原因而保留的,只要有可能,都应该使用 document.visibilityState。
6 p1 d& b4 K2 i# G5 o& i( ? 属性,而不是使用这个属性2.3 visibilitychange 事件只要 document.visibilityState 属性发生变化,就会触发 visibilitychange 事件;因此,可以通过监听这个事件(通过 。 : A. ?/ |7 Q6 b7 i" J9 j: [
document.addEventListener() 方法或 document.onvisibilitychange 属性),跟踪页面可见性的变化:document.addEventListener( " u/ ~. c: m& q1 N! z- [9 Z) J
visibilitychange,function(){// 用户离开了当前页面
$ P9 F( W9 Z4 ~0 {7 U if(document.visibilityState===hidden){document.title=页面不可见;}
) p8 n& C0 m3 \0 d; p // 用户打开或回到页面
. ]' T2 C, j/ U: p; S6 x if(document.visibilityState===visible){document.title=页面可见;}});上面代码是 Page Visibility API
! E4 ~* _" i9 Q! X 的最基本用法,可以监听可见性变化下面是另一个例子,一旦页面不可见,就暂停视频播放:constvidElem=document.getElementById(video-demo);document.addEventListener
3 T7 H$ @! p9 v2 w1 t7 ] (visibilitychange,startStopVideo);functionstartStopVideo(){if(document.visibilityState===hidden){vidElem # l3 ?7 ?1 w6 U* E k" {' B0 G. A- j
.pause();}elseif(document.visibilityState===visible){vidElem.play();}}2.4 页面卸载下面专门讨论一下,如何正确监听页面卸载页面卸载可以分成三种情况:。
2 M+ K* q" E+ v: l! I S" L5 R/ M 页面可见时,用户关闭 Tab 页或浏览器窗口页面可见时,用户在当前窗口前往另一个页面页面不可见时,用户或系统关闭浏览器窗口这三种情况,都会触发 visibilitychange 事件,前两种情况,该事件在用户离开页面时触发;最后一种情况,该事件在页面从可见状态变为不可见状态时触发。 ; s9 Y3 ~4 [5 c( Y
由此可见,visibilitychange 事件比 pagehide、beforeunload、unload 事件更可靠,所有情况下都会触发(从 visible 变为 hidden),因此,可以只监听这个事件,运行页面卸载时需要运行的代码,不用监听后面那三个事件。 ) J* H: k% ~/ W3 h( Z+ j
甚至可以这样说,unload 事件在任何情况下都不必监听,beforeunload 事件只有一种适用场景,就是用户修改了表单,没有提交就离开当前页面;另一方面,指定了这两个事件的监听函数,浏览器就不会缓存当前页面。 1 s) e J m9 J) |5 I# v& x2 Z
2 N0 u5 x& {( L7 _+ u( u
; `( a6 k3 W6 A4 [/ ]2 c# l6 e
& [1 t- q+ w. T7 Z; ]8 u7 Z$ V9 \ p* @9 A3 V3 R
|