網頁生命週期(Page Lifecycle API)

背景

apps的生命週期對於操做系統管理系統資源來講相當重要。在Android、IOS和Windows等的平臺上,系統能夠隨意啓動和停止apps的運行,這使得這些平臺可以從新分配資源,以提供最好的操做體驗給用戶。javascript

但對於web應用,並無相似的生命週期機制。隨着打開網頁數量增長,內存、CPU、網絡吞吐等系統關鍵資源都被過分使用,致使系統性能降低,下降了用戶的操做體驗。html

雖然瀏覽器很早以前就有提供關於生命週期的事件,好比loadunloadvisibilitychange等,可是這些事件只容許開發人員響應用戶自行發起的生命週期狀態更改。爲了讓網頁更可靠地運行,尤爲是在低功耗的設備(手機、智能手錶等),瀏覽器須要一種主動回收和分配系統資源的機制。java

Page Lifecycle API就是爲了解決這些問題而提出的方案,目標主要有三點:git

  1. 在web上引入並標準化生命週期狀態的概念
  2. 定義新的應用狀態,容許瀏覽器限制網頁持續佔據系統資源
  3. 建立新的API和事件,容許開發人員響應這些應用狀態之間的轉換

Page Lifecycle API已經在Chrome 68上獲得支持,下面將會進行詳細介紹。github


概覽

Page Lifecycle API定義了規範化的app生命週期狀態,且每一個頁面在一個階段只能處於一個狀態,每一個狀態的改變都會有相應的事件被觸發。話很少說,先上圖:web

page lifecycle

狀態

頁面狀態有6個,下面將分別展開描述,from表明的是前一個頁面狀態,to表明的是下一階段可能會變動到的狀態。windows

  1. Active

若是頁面可見並具備輸入焦點,則該頁面處於Active狀態api

from:passive(focus事件)瀏覽器

to: passive(blur事件)緩存

  1. Passive

若是頁面可見並不具備輸入焦點,則該頁面處於Passive狀態

from: avtive(blur事件)、hidden(visibilitychange事件)

to: avtive(focus事件)、hidden(visibilitychange事件)

  1. Hidden

若是頁面不可見且不處於frozen狀態,則該頁面處於hidden狀態

from: passive(visibilitychange事件)

to: passive(visibilitychange事件)、frozen(freeze事件)、terminated(pagehide事件)

  1. Frozen

若是網頁處於frozen狀態下,瀏覽器將暫停執行頁面任務隊列中的可凍結任務,直到頁面被解除凍結。這意味着像定時器和回調函數這樣的任務不會運行。

from: hidden(freeze事件)

to: active(resume和pageshow事件)、passive(resume和pageshow事件)、hidden(resume事件)

  1. Terminated

若是頁面開始被瀏覽器卸載並從內存中清除,它就處於terminated狀態。在此狀態下不能啓動任何新任務,若是現有運行時間太長,可能會被提早終止。

from: hidden(pagehide事件)

to: None

  1. Discarded

當瀏覽器爲了節省資源而卸載頁面時,它處於discarded狀態。任何類型的任務、事件回調或JavaScript代碼都不能在這種狀態下運行,由於丟棄一般發生在資源約束下,能夠理解爲頁面被動關閉,瀏覽器主動釋放資源。

from: frozen(無事件觸發)

to: None

事件

*表明的是Page Lifecycle API新提供的事件

  1. foucs

    DOM元素獲取焦點,前一個狀態通常是passive,當前狀態是avtive

  2. blur

    DOM元素失去焦點,前一個狀態通常是active,當前狀態是passive

  3. visibilitychange

    documentvisibilitySatate屬性發生變動,當用戶導航到新頁面、切換選項卡、關閉選項卡、最小化或關閉瀏覽器、或切換移動操做系統上的應用程序時,會觸發事件。前一個狀態是passivehidden,,當前狀態是passive或者hidden

  4. freeze*

    頁面被凍結,任務隊列中的可凍結任務都中止運行。前一個狀態是hidden,當前狀態是frozen

  5. resume*

    頁面解除凍結狀態,前一個狀態是frozen,當前狀態是activepassive或者hidden

  6. pageshow

    當一條會話歷史記錄被執行的時候將會觸發事件,包括了後退(前進)按鈕操做,同時也會在onload事件觸發後初始化頁面時觸發。前一個狀態可能爲frozen,當前狀態爲activepassivehidden

  7. pagehide

    pageshow相似,不一樣的就是導航離開當前網頁時觸發。先前的狀態可能爲hidden,當前狀態可能爲frozen或者terminated

  8. beforeunload

    窗口、文檔及其資源即將被卸載。文檔仍然可見,此時事件仍然能夠取消。前一個狀態可能爲hidden,當前狀態爲terminated

  9. unload

    卸載頁面時觸發,前一個狀態可能爲hidden,當前狀態爲terminated

新增功能

上面的圖表顯示了兩種系統觸發的頁面狀態:frozendiscard,這種頁面狀態和用戶觸發的不同,開發者沒法主動感知狀態變動。可是在Chrome 68上,開發者能夠監聽freezeresume兩個事件處理頁面狀態變動。

document.addEventListener('freeze', (event) => {
  // 頁面處於凍結狀態
});

document.addEventListener('resume', (event) => {
  // 頁面解凍
});
複製代碼

同時document對象新增了wasDiscarded屬性,這個屬性表明頁面是否處於discarded狀態,開發者能夠根據這個屬性的值處理不一樣的邏輯

if (document.wasDiscarded) {
  // 頁面被瀏覽器丟棄
}
複製代碼

觀察狀態

// active、passive和hidden三種狀態
const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};
複製代碼

另外,frozen和和terminated的狀態變動能夠經過監聽freezepagehide事件獲取。

封裝狀態觀察器

// 保存初始頁面狀態
let state = getState();

// 記錄狀態變動並打印在控制檯
// 更新當前頁面狀態
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// 監聽生命週期事件,保持頁面狀態更新
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState()), {capture: true});
});

// 監聽freeze事件
window.addEventListener('freeze', () => {
  // 頁面狀態變動爲frozen
  logStateChange('frozen');
}, {capture: true});

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    // event.persisted爲true意味着頁面是從緩存中加載的,因此是frozen狀態
    logStateChange('frozen');
  } else {
    // terminated狀態
    logStateChange('terminated');
  }
}, {capture: true});
複製代碼

跨平臺

Page Lifecycle這個標準剛剛引入,並無獲得所有瀏覽器平臺的支持。有些瀏覽器可能在切換標籤的時候不會觸發blur事件,有些瀏覽器沒有實現freezeresume事件,IE10如下版本不支持visibilitychange事件等等...

爲了讓開發者更容易上手處理跨平臺兼容的問題,谷歌開發了PageLifecycle.js這個庫,用於觀察頁面生命週期狀態的變化。PageLifecycle.js按事件觸發順序規範化處理了跨瀏覽器的差別,保證狀態變動和標準規範保存一致。


最佳實踐

對於開發者來講,理解頁面生命狀態並懂得根據頁面狀態不懂執行不一樣業務邏輯是很是重要的,下面介紹各個狀態下的最佳實踐:

  1. Active

    active狀態是用戶最關鍵的時間,所以也是頁面響應用戶輸入最重要的時間,任何可能阻塞主線程執行的非UI渲染任務均可以放到requestidlecallback或者web worker執行

  2. Passive

    在此狀態下,用戶沒有和頁面進行交互,但頁面仍然處於可視狀態,因此UI和動畫須要保持渲染狀態,從active狀態過渡到passive是一個保存頁面狀態的好時機,好比一些表單值。

  3. Hidden

    頁面被隱藏或者關閉,中止全部與用戶交互、UI渲染有關的任務,並及時保存應用狀態

  4. Frozen

    frozen狀態下,可凍結任務將會被掛起直到頁面解凍(有可能永遠不會發生)。在這個狀態下,開發者要作如下幾點:

    1. 關閉IndexedDB鏈接
    2. 關閉BroadcastChannel 鏈接
    3. 關閉WebRTC鏈接
    4. 中止http輪詢和websocket鏈接
    5. 中止定時器
  5. Terminated

    一般在此狀態下不須要處理任何任務,由於這個階段的任務不能保存可靠執行,有可能被強行終止。但你也能夠作一些狀態持久化或者埋點分析的任務


相關資料

  1. developers-google-web-page-lifecycle-api
  2. Page Lifecycle API
  3. windows-event-pageshow
相關文章
相關標籤/搜索