页面的生命周期

浏览器页面的生命周期

应用程序生命周期是现代操作系统管理资源的一种重要方式。在 Android、iOS 和最近的 Windows 版本上,应用程序可以由操作系统随时启动和停止。这使得这些平台能够精简和重新分配资源,使其最有利于用户。在网络上,历史上没有这样的生命周期,应用程序可以无限期地保持活力。随着大量网页的运行,内存、CPU、电池和网络等关键系统资源可能会被超额占用,导致终端用户体验不佳。虽然 Web 平台早就有了与生命周期状态相关的事件–比如加载、卸载和可见性改变,但这些事件只允许开发人员对用户发起的生命周期状态变化做出响应。要想让 web 在低功耗设备上可靠地工作(并且在所有平台上普遍更加注重资源),浏览器需要一种主动回收和重新分配系统资源的方法。

事实上,今天的浏览器已经在主动采取措施为后台标签页中的页面节约资源,许多浏览器(尤其是 Chrome)希望在这方面做得更多–以减少其整体资源占用。问题是开发者目前没有办法为这些类型的系统主动干预做准备,甚至不知道它们正在发生。这意味着浏览器需要保守,否则就会有破坏网页的风险。页面生命周期 API 试图通过以下方式来解决这个问题。

  • 引入网页生命周期状态的概念并使之标准化。
  • 定义新的、由系统发起的状态,允许浏览器限制隐藏的或不活动的标签消耗的资源。
  • 创建新的 API 和事件,使 Web 开发人员能够响应这些新的系统启动状态的转换。

该解决方案提供了网络开发者构建应用程序对系统干预的弹性所需的可预测性,它允许浏览器更积极地优化系统资源,最终使所有网络用户受益。

生命周期概述

所有的页面生命周期状态都是离散的、互斥的,也就是说一个页面一次只能处于一种状态。而页面生命周期状态的大部分变化一般都可以通过 DOM 事件来观察。也许最简单的解释页面生命周期状态以及在它们之间转换的信号事件的方法是用一张图。

生命周期概述

APIs

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

// 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 Back-Forward 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 }
);