今天的現代瀏覽器有時在系統資源受限的情境下會暫停頁面或徹底放棄執行它。未來,瀏覽器會主動執行此操做,所以它們會消耗更少的電量和內存。在Chrome 68中提供的Page Lifecycle API提供了生命週期鉤子,所以網頁能夠安全地處理這些瀏覽器干預,而不會影響用戶體驗。具體請查看API瞭解你的應用程序是否須要實現這些特性。javascript
應用程序的生命週期是現代操做系統管理資源的關鍵。在Android, iOS, 和最近的Windows版本中,操做系統能夠隨時開始或結束應用程序。這使得這些平臺能夠簡化和從新分配最有利於用戶的資源。html
在網絡上, 有史以來歷來沒有過這樣的生命週期, 因此應用程序能夠一直保持運行態。運行大量網頁後,內存,CPU,電池和網絡等關鍵系統資源可能會超負荷運行,從而致使最終用戶體驗不佳。java
雖然web平臺早就有與生命週期狀態相關的事件 — 如 load, unload, and visibilitychange—這些事件只容許開發者響應用戶形成的生命週期狀態更改。爲了使Web可以在低功耗設備上可靠地工做(而且一般在全部平臺上具備更高的資源意識),瀏覽器須要一種主動回收和從新分配系統資源的方法。git
事實上,如今的瀏覽器已經對在後臺標籤的頁面採起了積極措施來節約資源 , 並且不少的瀏覽器(特別是Chrome)會作更多這方面的工做 - 以減小它們的總體資源佔用。github
問題是開發者目前沒法爲系統執行的這類干預操做作好準備,甚至都不知道系統正在干預。 這意味着瀏覽器須要保護或冒險破壞網頁。web
Page Lifecycle API 經過如下措施來解決這個問題:chrome
在網上介紹並標準化生命週期狀態的概念。數據庫
定義新的系統啓動狀態,容許瀏覽器限制隱藏或非活動狀態的標籤頁可使用的資源。api
建立新的API和事件,容許Web開發者響應這些新的系統啓動狀態的轉換。瀏覽器
這種解決方法提供了可預測性,網頁開發人員須要建立一個能靈活應對系統干預的應用程序,並且這種解決方法讓瀏覽器能夠更加積極地優化系統資源,最終令全部web用戶受益。
本篇文章剩餘部分會介紹Chrome 68中新的頁面生命週期特性,而且會探索這些特性與全部已存在的網頁平臺的狀態和事件的關聯。文章也爲開發者應該(或不該該)在每一個狀態進行的工做類型提供建議和最佳實踐。
全部的頁面生命週期狀態都是相互獨立存在的, 也就是說一個頁面在同一個時間點只能存在一個狀態。並且一般大多數頁面生命週期的狀態改變均可經過DOM事件監聽 ( 也有例外,查看開發者對每一個狀態的建議 ).
也許圖表是最能直觀解釋頁面生命週期狀態的,它一樣也能很好的標識事件間的轉換:
下表詳列了每一個狀態的細節信息。還列出了可能發生的先後狀態,還包括開發者能夠用來監聽變化的事件。
狀態 | 描述 |
---|---|
Active |
當頁面可見或有input框聚焦時,該頁面處於active狀態
可能以前的狀態爲: passive (經過focus 事件轉換而來)
可能下個狀態爲: passive (經過 blur 事件轉換而來)
| | Passive |
當頁面可見但沒有聚焦的input框時,該頁面處於passive狀態
以前可能的狀態是: active (經過觸發blur 事件而來) hidden (經過visibilitychange觸發事件而來)
接下來可能變成的狀態是: active (經過觸發focus 事件而來) hidden (經過觸發visibilitychange 事件而來)
| | Hidden |
若是頁面不可見且還沒有被凍結,則處於hidden狀態。
以前可能的狀態是: passive (經過事件visibilitychange而來)
接下來可能變成的狀態: passive (經過觸發visibilitychange事件而來) frozen (經過觸發freeze事件而來) terminated (經過觸發pagehide事件而來)
| | Frozen |
當處於frozen狀態時,瀏覽器暫停執行頁面裏任務隊列 中 可凍結的任務 直到頁面不是凍結狀態. 也就是說像JavaScript定時器和fetch回調不會運行。已經運行的任務能夠結束(特別是freeze回調), 但它們可能會受限於它們作的是什麼或能運行多久。
瀏覽器凍結頁面是爲了保護CPU/電池/數據使用,一樣它也能使後退/前進導航更快— 由於避免了從新加載整個頁。
可能以前的狀態爲: hidden (經過觸發freeze事件而來)
_
以後可能變成的狀態: active (經過先觸發resume事件, 再觸發pageshow事件而來) passive(經過先觸發 resume事件,再觸發pageshow事件而來) hidden (經過觸發resume事件而來)
_ | | Terminated |
一旦瀏覽器卸載頁面或在內存中清除頁面時,頁面就變爲terminated狀態. 在這種狀態下不會運行新任務, 而且正在進行的任務若是運行了過久也會被殺掉。
以前可能的狀態是: hidden (經過觸發pagehide事件)
以後可能的狀態是: NONE
| | Discarded |
當頁面被瀏覽器卸載爲保護資源時,頁面處於discarded狀態.在這種狀態下不會運行任何任務、事件回調甚至是JavaScript。由於在資源受限的狀況下一般要放棄某些操做, 因此不可能開啓一個新進程。
處在discarded 狀態的tab頁 (包括tab頁窗口的標題和圖標 ) 即便是頁面消失也老是對用戶可見
以前可能的狀態是: frozen (不觸發事件)
以後可能的狀態是: NONE
|
瀏覽器會發送許多事件,可是隻有小部分事件代表頁面週期狀態可能發生變化。下表概述了與生命週期相關的全部事件,並列出了它們可能轉換先後的狀態。
名字 | 細節信息 |
---|---|
focus |
有DOM元素已經聚焦。
備註: focus事件並不必定表示狀態改變了。若是頁面以前沒有input聚焦,它僅表示狀態更改。
以前可能的狀態是: passive
當前可能的狀態: active
| | blur |
有DOM元素失去焦點。
備註: blur事件並不必定表示狀態改變了。若是頁面再也不有input框聚焦(例如, 頁面沒有從一個聚焦的元素切換到另外一個元素), 它僅表示狀態更改。
以前可能的狀態是: active
當前可能的狀態: passive
| | visibilitychange |
文檔上, visibilityState值已經更新了。文檔的visibilityState值已更改。 當用戶導航到新頁面,切換選項卡,關閉選項卡,最小化或關閉瀏覽器或在移動端切換應用程序時,可能會使visibilityState的值改變。
| | freeze * |
頁面剛被凍結. 頁面任務隊列不會執行任何凍結的任務。
以前可能的狀態: hidden
當前可能的狀態: frozen
| | resume * |
The browser has resumed a frozen page. 瀏覽器已經恢復了凍結的頁面
以前可能的狀態: frozen
當前可能的狀態: active (若是是緊隨pageshow 事件發生) passive (若是是緊隨pageshow事件發生) hidden
| | pageshow |
會話歷史記錄新增一條記錄。
這多是個全新的頁面,也多是導航緩存中的頁面. 若是頁面是頁面導航緩存中,事件的持久屬性爲true,不然爲false。
可能以前的狀態: frozen (resume 事件也會被觸發)
| | pagehide |
會話歷史記錄新增一條記錄。
若是用戶在瀏覽另外一個頁面, 並且瀏覽器可能會添加當前的頁面到頁面導航緩存 ,以便以後調用, 事件屬性會持續爲true,此時頁面將進入到frozen狀態, 不然會進入到結束狀態。
可能以前的狀態是: hidden
當前可能的狀態是: frozen (event.persisted爲true, freeze事件緊隨) terminated(event.persisted爲false, unload事件緊隨)
| | beforeunload |
window、document以及其資源即將被卸載。但在此時,document仍然是可見的,事件仍能夠被取消。
警告: beforeunload事件只能用於警告用戶有未保存的改變。一旦保存後,事件應該會被清除。這樣作不可能對頁面沒有影響,由於在某些場景下會犧牲性能。在舊版API看詳細內容.
以前可能的狀態是: hidden
當前可能的狀態是: terminated
| | unload |
此時頁面正在卸載
警告: 建議千萬不要使用unload事件,由於它不穩定並且在某些場合下可能傷害性能。在舊版API 看詳細內容.
以前可能的狀態是: hidden
當前可能的狀態: terminated
|
* 如下展現頁面生命週期API定義的新事件
上面的部分展現了兩種狀態,它是系統初始化狀態而非用戶初始化狀態:frozen 和 discarded. 正如以上提到的,如今的瀏覽器偶爾會凍結並丟棄隱藏的標籤(他們本身決定), 可是開發者沒法得知。
在Chrome 68, 開發者如今能夠經過監聽document的freeze和resume事件來觀察一個隱藏的tab標籤何時凍結和解除凍結的.
document.addEventListener('freeze', (event) => { // The page is now frozen. }); document.addEventListener('resume', (event) => { // The page has been unfrozen. });
在chrome 68的文檔對象中如今也包含了 wasDiscarded 屬性. 它用於決定在隱藏的標籤中什麼時候被拋棄。你能夠在頁面加載時檢查這個值(備註:被拋棄後的頁面要從新用必須從新加載).
if (document.wasDiscarded) { // Page was previously discarded by the browser while in a hidden tab. }
若想了解關於在freeze和resume事件發生時該作哪些重要操做的建議,或想知道頁面即將被拋棄時如何處理和準備,請查看 對每一個狀態的開發者建議.
接下來的幾個章節歸納了這些新特性如何適應已經存在的web平臺的狀態和事件。
在active, passive, 以及 hidden 這些狀態時,能夠從如今的web平臺API中執行一些JavaScript代碼判斷當前頁面生命週期狀態。
const getState = () => { if (document.visibilityState === 'hidden') { return 'hidden'; } if (document.hasFocus()) { return 'active'; } return 'passive'; };
但frozen 和 terminated狀態,當狀態改變時只能在相應的事件(freeze 和pagehide) 中才能監聽到。
基於上面定義的getState()函數,能夠做以下修改,這樣即可觀察全部頁面生命週期狀態改變。
// Stores the initial state using the getState() function (defined above). let state = getState(); // Accepts a next state and, if there's been a state change, logs the // change to the console. It also updates the state value defined above. const logStateChange = (nextState) => { const prevState = state; if (nextState !== prevState) { console.log(State change: ${prevState} >>> ${nextState}); state = nextState; } }; // These lifecycle events can all use the same listener to observe state // changes (they call the getState() function to determine the next state). ['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => { window.addEventListener(type, () => logStateChange(getState()), {capture: true}); }); // The next two listeners, on the other hand, can determine the next // state from the event itself. window.addEventListener('freeze', () => { // In the freeze event, the next state is always frozen. logStateChange('frozen'); }, {capture: true}); window.addEventListener('pagehide', (event) => { if (event.persisted) { // If the event's persisted property is true the page is about // to enter the page navigation cache, which is also in the frozen state. logStateChange('frozen'); } else { // If the event's persisted property is not true the page is // about to be unloaded. logStateChange('terminated'); } }, {capture: true});
以上代碼作了三件事:
使用getState()函數設置初始狀態。
定義一個入參爲下個狀態值的函數, 並且若是有狀態值有改變,在控制檯打印出這些改變。
爲全部必要的生命週期事件添加捕獲事件監聽器,反過來調用logStateChange()函數, 傳入須要改變的狀態值爲參數。
警告! 這段代碼在不一樣的瀏覽器中會產生不一樣的結果, 由於事件順序(以及可靠性)實現還未統一。查看管理跨瀏覽器差別學習處理這些差別的最佳實踐.
對上面的代碼要提醒一點, 全部的事件監聽器都要被添加到window並且他們都會傳入{capture: true}. 這麼作有幾個緣由:
不是全部的生命週期事件的target都一致。pagehide和pageshow事件會在window上觸發; visibilitychange、freeze以及resume事件在document上觸發, 而focus和blur事件會在他們各自的DOM元素上觸發。
這些事件大部分不會冒泡,這就意味着不可能在公共的祖先上添加非捕獲事件監聽器監聽全部的事件
捕獲階段在目標或冒泡階段以前執行, 因此在此時添加事件監聽器能確保它能在其餘代碼取消它們前執行。
本篇文章的開始根據頁面生命週期API概述了狀態和事件流。但因爲這些API剛被引入, 新的事件和DOM API還未在全部的瀏覽器中實現。
此外, 當下全部瀏覽器實現的事件也並未一致。例如:
當切換標籤頁時有些瀏覽器滅有觸發blur事件。這就意味着(跟上面在表格和圖表中的相反)頁面能夠直接從active 狀態直接進入到hidden狀態而不會先轉爲passive狀態。
個別瀏覽器實現了頁面導航緩存, 而頁面生命週期API定義了緩存的頁面應處於凍結狀態。因爲API徹底是新的,因此這些瀏覽器還未實現freeze和resume事件, 即便這些狀態仍然能夠經過pagehide和pageshow事件被監聽到。
IE瀏覽器老版(10及如下版本)沒有實現visibilitychange事件。
pagehide和visibilitychange事件的發生順序有所 改變. 若是在卸載頁面時,而且頁面處於可見狀態,早期瀏覽器會先觸發pagehide事件再觸發visibilitychange事件。新的Chrome版本則是先觸發visibilitychange事件再觸發pagehide事件,不管卸載時文檔是否爲可見狀態。
爲了讓開發者更容易處理這些跨瀏覽器的矛盾問題,並能全心關注生命週期狀態建議以及最佳實踐, 咱們發佈了PageLifecycle.js, 這是個用來監聽頁面生命週期API狀態改變的JavaScript庫。
PageLifecycle.js 規範了跨瀏覽器在事件觸發順序的差別,這樣狀態就能準確如本文圖表及表格中所述變化(並且在全部的瀏覽器中都能保持一致).
做爲開發者,瞭解頁面生命週期_以及_知道在代碼中如何監聽它們都一樣重要,由於你接下來應該作的(不該該作的)工做都會極大的依賴於當前頁面的狀態。
例如,若是頁面處於hidden狀態,很明顯給用戶瞬時性通知沒有意義。因爲這個例子很是明顯,但總有不那麼明顯的場景,如下例舉了其中值得關注的場景的建議.
狀態 | 開發者建議 |
---|---|
Active |
對用戶來講active狀態是最關鍵的,所以這是最好的時間響應用戶輸入.
任何可能阻止主線程的非UI工做都應該被從新劃分爲空閒時段 或 卸載到Web worker.
| | Passive |
在passive狀態時, 用戶不會與頁面互動,但仍對用戶可見。這也就是說UI更新及動畫依然會流暢,但這些更新時間就沒那麼重要了。
當頁面從active更改成passive時,如今是保持未保存的應用程序狀態的好時機。
| | Hidden |
當頁面從passive狀態切換到hidden狀態時,可能用戶不會再跟它有交互直到頁面被從新加載.
開發者能可靠的監聽到最後狀態變化多是頁面轉換到hidden狀態。 (特別是在移動設備上, 由於用戶能夠關掉tab標籤或者是瀏覽器,此時, beforeunload, pagehide, unload事件都不會觸發).
這意味着您應該將hidden狀態視爲用戶會話可能結束. 換言之,這時該保存全部沒有保存的應用狀態、發送還未發送的全部須要分析的數據。
這時候也應該中止更新UI(由於用戶都不會看到了),也要關閉全部用戶不但願在後臺運行的程序。
| | Frozen |
頁面處於frozen狀態時,在 任務隊列 凍結的任務 會被掛起直到頁面再也不被凍結——這可能永遠不會發生(例如: 頁面被拋棄).
這意味着頁面從hidden轉爲frozen時,必須中止任何計時器或拆除任何鏈接,若是凍結,可能會影響同一源的其餘open的選項卡標籤,或影響瀏覽器將頁面放入頁面導航緩存的能力.
特別值得一提的是:
關閉全部打開的IndexedDB鏈接。
關閉打開的BroadcastChannel鏈接。
關閉打開的WebRTC鏈接.
中止全部網絡輪詢以及全部打開的Web Socket鏈接.
釋放全部被保持的Web Locks.
應該將任何動態的視圖狀態(e.g. 無限滾動列表視圖的滾動位置) 保存到sessionStorage (或者經過commit()提交到IndexedDB ) 這樣的話,若是隨後頁面被拋棄又從新加載就能恢復原來的狀態。
若是頁面從frozen轉變爲hidden,你能夠從新打開在任何開始凍結狀態時關閉的鏈接或者重啓那時被中止的輪詢。
| | Terminated |
一般在頁面轉爲terminated狀態時不須要作任何操做。
因爲頁面即將被卸載,致使用戶行爲老是在進入terminated狀態前進入hidden狀態,進入hidden狀態時應該執行會話結束邏輯(例如,保存應用程序狀態和提交分析數據).
固然 (正如對hidden狀態建議中提到的), 對開發者而言瞭解轉爲terminated狀態在許多場景下(特別是移動設備上)是不可靠的極爲重要, 因此依靠termination事件(例如,beforeunload, pagehide, 還有unload事件)都有可能丟失數據。
| | Discarded |
當頁面正被拋棄時, discarded狀態不會被開發者監聽到。這是由於頁面一般在資源約束下被丟棄,而且在大多數狀況下根本不可能解凍頁面以容許腳本運行響應丟棄事件.
因此, 在從hidden轉變到frozen時,你應該作好有可能頁面會被拋棄的準備。這樣的話,在頁面加載時,經過檢查document.wasDiscarded屬性來恢復拋棄的頁面狀態。
|
再次重申, 因爲不一樣的瀏覽器對生命週期事件的可靠性及順序實現不統一。若要根據上表的建議最簡單的是使用 PageLifecycle.js.
關鍵點: 千萬別再現代瀏覽器中使用unload事件。
許多開發者認爲unload事件是能保證會回調,因此使用它做爲結束會話的信號來保存狀態或者發送分析數據。但這樣作極爲不靠譜, 特別是在移動設備上! unload事件在一些典型的unload場景中並無被觸發,包括從移動設備上標籤切換器上關閉標籤還有從app切換器上直接關閉瀏覽器app.
因爲這種緣由, 依賴visibilitychange 事件來決定會話是否結束更好,並且能夠把hidden狀態當作 最後一次最可靠的事件來保存app及用戶數據.
另外, 僅僅在已註冊的卸載事件處理程序(經過onunload或addEventListener())就能夠防止瀏覽器將頁面放入 頁面導航緩存 中,以實現更快的後退和前進加載。
現代瀏覽器(包括IE11), 都建議始終用pagehide 事件來檢測頁面是否被卸載 (a.k.a terminated狀態) 而不是用unload事件. 若是你須要支持Internet Explorer版本10及如下, 您應該檢測pagehide事件,若是瀏覽器不支持pagehide,則只使用unload:
const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload'; addEventListener(terminationEvent, (event) => { // Note: if the browser is able to cache the page, event.persisted // is true, and the state is frozen rather than terminated. }, {capture: true});
想要了解更多關於頁面導航緩存,以及unload事件爲什麼會破壞它們,請看:
關鍵點: 永遠不要無條件地添加beforeunload監聽器或將其用做爲會話結束信號。 僅在用戶未保存的工做時添加,並在保存工做後當即將其刪除。
beforeunload和unload事件的問題相似,由於當它發生時會阻止瀏覽器在其頁面導航緩存中緩存頁面
beforeunload和unload事件的不一樣之處是使用beforeunload是被容許的. 例如,當你想要警告用戶若是繼續卸載頁面未保存的變化將會丟失。
因爲有正當理由使用beforeunload,但使用它會阻止頁面添加到頁面導航緩存中。因此建議只在用戶有未保存的變化狀態時才添加beforeunload監聽,在保存好這些改變當即刪除。
換句話說,不要這樣作(由於它無條件地添加了一個beforeunload監聽器):
addEventListener('beforeunload', (event) => { // A function that returns true if the page has unsaved changes. if (pageHasUnsavedChanges()) { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?'; } }, {capture: true});
應該這樣處理(只在須要的時候添加beforeunload監聽, 在不須要的時候刪除監聽處理器):
const beforeUnloadListener = (event) => { event.preventDefault(); return event.returnValue = 'Are you sure you want to exit?'; }; // A function that invokes a callback when the page has unsaved changes. onPageHasUnsavedChanges(() => { addEventListener('beforeunload', beforeUnloadListener, {capture: true}); }); // A function that invokes a callback when the page's unsaved changes are resolved. onAllChangesSaved(() => { removeEventListener('beforeunload', beforeUnloadListener, {capture: true}); });
備註: PageLifecycle.js 庫提供了簡便方法addUnsavedChanges()和removeUnsavedChanges(), 都是會遵循以上概述的最佳實踐. 它們基於一個提案草案,正式用聲明性API替換beforeunload事件,該API在移動平臺上不易被濫用且更可靠。
若你想正確地使用beforeunload事件而且能跨瀏覽器工做,建議使用PageLifecycle.js。
在hidden狀態下運行時,有不少合理的場景網頁都不該被凍結。最多見的例子是當app在播放音樂時。
在某些狀況下Chrome丟棄頁面會很危險,例如假設頁面包含有未提交用戶輸入的表單,或是有個在頁面卸載時發出警告的beforeunload處理程序。
目前, 在丟棄頁面時Chrome將更保守,只有在有把握不會影響用戶時纔會這樣作。例如,在hidden狀態時,頁面會監聽是否有以下狀況,如有Chrome不會丟棄該頁面,除非資源極其受限:
播放音頻
正在使用WebRTC
更新表格標題或圖標時
彈出警告時
發送推送通知
備註: 對於更新標題或網站圖標以提醒用戶未讀通知的網頁,咱們目前 目前有一項提議,從service worker來更新,這容許Chrome凍結或丟棄頁面,但仍然會顯示更改的tab標題或網站圖標。
頁面導航緩存是個通常術語,用於描述一些瀏覽器實現更快前進或後退按鈕的導航優化。Webkit把它叫作頁面緩存 而Firefox稱它爲Back-Forwards Cache (或是簡寫bfcache).
當用戶離開頁面時,這些瀏覽器會凍結該頁面的版本,以便用戶使用前進或後退按鈕導航時能快速恢復。請記住,添加beforeunload或unload事件處理器 能阻止優化.
歸根結底,這個凍結與瀏覽器爲了保護CPU/電量所作的凍結都有一樣的功能; 所以這種狀況也被認爲是frozen生命週期狀態.
頁面生命週期API定義的狀態是相互獨立,沒有關聯的。因爲頁面能在active、passive、或者hidden狀態時均可以加載,單獨的loading狀態沒有意義存在。並且也由於load和DOMContentLoaded事件並不標識頁面生命週期狀態變化,因此它們與API沒有關係.
在frozen或terminated狀態下, 在頁面 任務隊列裏的凍結任務 被掛起,也就是說不能可靠的調用異步操做或基於API的回調(如IndexedDB)。
未來, 咱們將爲IDBTransaction對象添加commit()方法, 這樣爲開發者提供了高效執行只寫的數據處理方法而無需回調.換言之, 若是開發者僅僅是想將數據寫入到IndexedDB而不需執行包含一系列讀寫數據的複雜數據處理,那麼commit()方法能在任務隊列被掛起前完成。 (假設IndexedDB數據庫已經打開).
可是,對於今天須要工做的代碼,開發人員有兩種選擇:
使用Session Storage: Session Storage是同步的並且跨頁丟棄持久保存。
從service worker中使用IndexedDB: 在頁面被終止或丟棄時,service worker能夠將數據存在IndexedDb. 在freeze或pagehide事件監聽器中能夠經過postMessage()發送數據到service worker, service worker能夠處理保存數據.
備註: 雖然上面的第二種方法能解決問題,可是在因爲內存壓力致使設備凍結或丟棄的場景下,這種方案就不是很理想。由於瀏覽器須要啓動service worker進程,這會帶來更大的壓力。
要在frozen和discarded狀態下測試app行爲, 能夠訪問 chrome://discards凍結或丟棄任何你打開的標籤頁。
這確保你當頁面在丟棄後重加載時能正確的處理freeze和resume事件以及document.wasDiscarded 標誌
開發者若想要尊重用戶設備的系統資源,要時刻牢記在app中使用壓面生命週期狀態。 在用戶不指望的狀況下,網頁不會消耗過多的系統資源,這一點相當重要。
此外, 越多的開發者開始實現新的生命週期API,瀏覽器凍結或丟棄不使用的頁面就越安全。這就意味着瀏覽器會消耗更少的內存、CPU、電量, 這對用戶也是件好事。
最近, 開發者若想要實現本文提到的 最佳實踐,但又不想去記全部可能的狀態及事件轉換那就使用 PageLifecycle.js吧,這樣能夠很容易在全部瀏覽器監聽到一致的生命週期狀態變化。