Principles of front-end performance optimization - Page Lifecycle

Principles of front-end performance optimization - Page Lifecycle

guide

Original link

Performance has always been a topic that has been tirelessly discussed by the front end. When it comes to the life cycle, everyone must be familiar with it. This is the core concept of the framework design. It generally provides timing hooks such as data initialization, DOM loading, and resource release for everyone to use reasonably. For example, performance optimization in Reactscenarios It plays an important role. In the same way, the design concept of life cycle has been introduced and implemented by modern browsers, and understanding the latest standard specifications is very important for the front-end to understand the direction of performance optimization. This article mainly introduces the APIs related to the browser tab page life cycle, some suggestions, and the practice of data reporting and collection.VueReact shouldComponentUpdate

Lifecycle overview

curious

Google and other browsers have been complained about as memory-swallowing monsters, but have you noticed that it has been improved now? When a Tab has not been activated for a long time, you will find that the page will be refreshed when you open it again. When will the occupied resources be recycled?

It is found on mobile devices that when the browser is switched to the background, some tasks on the webpage will be reduced in frequency (such as timer), or even stop running. How is the browser optimized to reduce the use of memory, CPU, power, and network resources? What behaviors of development interfere with optimization?

To understand these issues, we need to invite today's protagonist - W3C's latest specification Page Lifecycle .

solved problem

Page Lifecycle APITry to solve the performance bottleneck by:

  1. Introduces and standardizes the concept of lifecycle states on the Web.
  2. Defines a new system startup state that allows the browser to limit the resources available to hidden or inactive tabs.
  3. Create new APIs and events that allow web developers to respond to transitions between these new system startup states.
    The solution provides the predictability that web developers need to build applications that are resilient to system intervention and allows browsers to more aggressively optimize system resources.

Lifecycle states and events

All page lifecycle states are discrete and mutually exclusive, meaning that a page can only be in one state at a time.

  • Lifecycle state transitions and triggered events
    page-lifecycle-api-state-event-flow

state

state describe Possibly previous state (triggering event) Possible next state (trigger event)
Active The page is visible document.visibilityState === 'visible'and has inputfocus 1. passive (focus) 1. passive (blur)
Passive The page is visible and no input is in focus 1. active (blur)
2. hidden (visibilitychange)
1. active (focus)
2. hidden (visibilitychange)
Hidden The page is invisible document.visibilityState === 'hidden'and not frozen 1. passive (visibilitychange) 1. passive (the visibilitychange)
2. frozen (freeze)
3. terminated (pagehide)
Frozen frozenThe state browser suspends the execution of freezeable tasks in the task queue, which means that e.g. JS timeror fetchcallbacks will not execute. Executing tasks can be completed, but the operations that can be performed and the running time will be limited.

Browser freezing is to save CPU, memory, and power consumption. At the same time, it makes forward and backward faster, avoiding reloading the full page from the network
1. hidden (freeze) 1. active (resume -> pageshow)
2. passive (resume -> pageshow)
3. hidden (resume)
Terminated terminatedThe status indicates that the browser has unloaded the page and reclaimed resource occupation, no new tasks will be executed, and long running tasks may be cleared. 1. hidden (pagehide) none
Discarded discardedThe status occurs when the system resources are limited, and the browser will actively unload the page to release memory and other resources for new entries/threads. No tasks, event callbacks, or any kind of JS can execute while in this state. Although the page is gone, the tab name and favicon of the browser Tab page are still visible to the user 1. frozen (no events fired) none

event

All events related to the lifecycle are described below, and their possible transition states are listed.

focus

  • Description: DOM element gets focus
  • previous possible state
  1. passive
  • current possible state
  1. active
  • Note: The focus event does not always trigger a lifecycle state change, only if the page was not focused before.

blur

  • Description: The DOM element loses focus
  • previous possible state
  1. active
  • current possible state
  1. passive
  • Note: The blur event does not always trigger a lifecycle state change, only when the page is no longer focused. For example switching focus between page elements will not.

visibilitychange

  • Description: document.visibilityStateValue changes. Trigger scene:
  1. Refresh or navigate to a new page
  2. Switch to new tab page
  3. Close Tab, minimize, or close the browser
  4. Switch apps on the mobile terminal, such as pressing the Home button, clicking the head notification switch, etc.
  • previous possible state
  1. passive
  2. hidden
  • current possible state
  1. passive
  2. hidden

freeze

  • Description: The page is frozen, and the freezeable tasks in the task queue will not be executed.
  • previous possible state
  1. hidden
  • current possible state
  1. frozen

resume

  • Description: The browser restarted a frozen page
  • previous possible state
  1. frozen
  • current possible state
  1. active (if followed by the pageshow event)
  2. passive (if followed by the pageshow event)
  3. hidden

pageshow

  • Description: Retrieve whether the page navigation cache exists, if it exists, take it out of the cache, otherwise load a brand new page.
    If the page is fetched from the navigation cache, the event property persistedis true, otherwise it is false.
  • previous possible state
  1. frozen (the resume event will also trigger at this time)
  • current possible state
  1. active
  2. passive
  3. hidden

pagehide

  • Description: Whether the page session can be stored in the navigation cache.
    The event property persistedis true if the user navigated to another page and the browser was able to add the current page to the page navigation cache for later reuse . If true, the page will enter frozenthe state , otherwise it will enter terminatedthe state.
  • previous possible state
  1. hidden
  • current possible state
  1. frozen (event.persisted is true, freeze event follows)
  2. terminated (event.persisted is false, unload event follows)

beforeunload

  • Description: The current page is about to be unloaded. At this point, the content of the document on the current page is still visible, and closing the page can be canceled at this stage.
  • previous possible state
  1. hidden
  • current possible state
  1. terminated
  • Warning: The listener beforeunloadevent is only used to remind the user that there are unsaved data changes. Once the data is saved, the listener event callback should be removed.
    It should not be added to the page unconditionally, as doing so can hurt performance in some cases.

unload

  • Description: The page is being unloaded.
  • previous possible state
  1. hidden
  • current possible state
  1. terminated
  • WARNING: Listening to unloadevents as it is unreliable and may affect performance in some cases.

system event

frozen and discarded are system behaviors rather than user active behaviors. Modern browsers do not see things on the tab page, and may actively freeze or discard the current page.
Developers do not know how these two occur.

The freeze and resume events are provided in Chrome 68+. When the page changes from the hidden state to the frozen and non-frozen state, developers can monitor documentand know .

document.addEventListener('freeze', (event) => {
    
    
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
    
    
  // The page has been unfrozen.
});

And provides document.wasDiscardeda property to get the currently loaded page, whether it was discarded when it was invisible before.

if (document.wasDiscarded) {
    
    
  // Page was previously discarded by the browser while in a hidden tab.
}

Ways to monitor some lifecycle states

get active, passive,hidden

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

States like frozenand terminatedrequire monitoring freezeand pagehideevent acquisition.

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

The code above does three things:

  1. getState()initialization state;
  2. Define logStateChangethe function to receive the next state, if it changes, console;
  3. Listen for 捕获阶段events , one call logStateChange, incoming state changes.

Note: The above order of console printing may be inconsistent in different browsers.

Expansion: Why pass in the third parameter {capture: true}and windowlisten to events on

  1. Not all lifecycle events are created equal target;
    1. pagehide, triggered onpageshow ;window
    2. visibilitychange, freeze, trigger onresume ;document
    3. focus, blurtriggered on the corresponding DOM element.
  2. Most events do not bubble, which means that in the bubbling phase, it windowcannot ;
  3. The capture phase occurs between the target phase and the bubbling phase, which means that capture phase events are not canceled by other bubbling events.

cross-browser compatible

Since the lifecycle API has just been introduced, the new events and DOM APIs are not implemented in all browsers. Also, all browser implementations are not consistent.
For example:

  1. When some browsers switch Tab, blurthe event , which means that activethe state does not pass through passivethe state and becomes directlyhidden
  2. Although some browsers have realized page navigation cachethat Page Lifecycle APIthe cached pages are classified as frozen, they have not yet been implemented freeze. resumeWaiting for the latest API, although the non-frozen state can also be passed pageshow, and pagehidethe event can be monitored.
  3. Not implemented in IE 10 and below pagehideEvents
  4. pagehide, visibilitychangeThe trigger sequence has been changed . When the page is being unloaded, if the page is visible, it will pagehidefire visibilitychange. In recent versions of Chrome, fires visibilitychangefirst pagehide.
  5. Safari closing the tab may not trigger pagehideor visibilitychange. It needs to be monitored beforeunloadfor compatibility, beforeunloadand it needs to end the bubbling phase to know whether the state has changed hidden, so it is easy to be canceled by other events.

PageLifecycle.js is recommended to ensure cross-browser consistency.

Advice and practice

By understanding the life cycle state, developers can more clearly know which page states to perform and which tasks to avoid in order to optimize performance.

Advice for each state

state suggestion
Active This state is the most important stage for the user, when it is most important to respond to user input. Non-no-UI tasks that block the main thread for a long time can be handed over to idlea period or web workerprocess
Passive In this state, the user is not interacting with the page, but they can still see it. This means UI updates and animations should still be smooth, but the timing of those updates happening is less critical. When the page activechanges passiveto , it's a good time to store unsaved data.
Hidden When passivetransitioning to hidden, there's a good chance the user won't interact with the page until it's reloaded.

hiddenThe state is often the last state that developers can rely on, especially on the mobile side, such as when switching APPs beforeunload, pagehideand unloadevents will not be triggered.

This means that, for developers, hiddenthe state as the final state of the page session. At this time, the unsaved application data should be persisted, and the data collected and reported for analysis should be collected.

At the same time, you should stop the UI update, because the user can't see it anymore. It is also necessary to stop tasks that users do not want to perform in the background to save resources such as battery.
Frozen In frozenstate , freezable tasks in the task queue are suspended until the page is unfrozen (which may never happen, e.g. if the page is discarded ). At this time, it is necessary to stop and close all connections ( IndexedDB , BroadcastChannel , WebRTC , Web Socket connections. Release Web Locks ), it should not affect other open same-origin pages or affect the browser's storage of pages in the cache (page navigation cache). You should also persist dynamic view information (such as the sliding position of the infinite sliding list) to sessionStorage or IndexedDB via commit(), so that it can be reused after discarded and reloaded. When the state changes back to , you can reopen any closed connections, or restart any polling that was stopped when the page was initially frozen.discarded

timer



hidden
Terminated 当页面变成 terminated 状态,开发人员一般不需要做任何操作。因为用户主动卸载页面时总会在 terminated 之前经历 hidden 状态(页面刷新和跳转时不一定会触发 visibilitychange,少部分浏览器实现了,大部分可能需要 pagehide 甚至beforeunloadunload 来弥补这些场景),你应该在 hidden 状态执行页面会话的结束逻辑(持久化存储、上报分析数据)。

开发人员必须认识到,在许多情况下(特别是在移动设备上),无法可靠地检测到终止状态,因此依赖终止事件(例如beforeunloadpagehideunload)可能会丢失数据。
Discarded 开发人员无法观察到被废弃的状态。因为通常在系统资源受限下被废弃,在大多数情况下,仅仅为了允许脚本响应discard事件而解冻页面是不可能的。因此,没必要从hidden更改为frozen时做处理,可以在页面加载时检查 document.wasDiscarded,来恢复之前被废弃的页面。

避免使用老旧的生命周期API

  • unload,不要在现代浏览器中使用
  1. 很多开发人员会把 unload 事件当做页面结束的信号来保存状态或上报分析数据,但这样做非常不可靠,特别是在移动端。
    unload 在许多典型的卸载情况下不会触发,例如通过移动设备的选项卡切换、关闭页面或系统切换器切换、关闭APP。
  2. 因此,最好依赖 visibilitychange 事件来确定页面会话何时结束,并将 hidden 状态视为最后保存应用和用户数据的可靠时间。
  3. unload 会阻止浏览器把页面存入缓存(page navigation cache),影响浏览器前进后退的快速响应。
  4. 在现代浏览器(包括IE11),推荐使用 pagehide 事件代替 onload 监测页面卸载(terminated)。onload 最多用来兼容IE10。
  • beforeunload,和 unload 有类似的问题,仅仅用来提醒用户关闭或跳转页面时有未保存的数据,一旦保存立即清除。

数据采集上报启发与实践

  1. 避免在 loadDOMContentLoadedbeforeunloadunload 中处理上报采集数据。
  2. 监听 visibilitychange 在各种切换APP、息屏时处理采集信息。
  3. 监听 pagehide 收集页面刷新导航跳转场景。
  4. 仅仅使用 beforeunload 兼容 Safari 关闭 Tab 和IE11以下版本的场景。
  5. 注意一旦收集信息立即销毁所有采集事件,避免重复上报。
function clear(fn) {
    
    
  ['visibilitychange', 'pagehide', 'beforeunload']
    .forEach(event => window.removeEventListener(event, fn, true));
}

function collect() {
    
    
  const data = {
    
     /*  */ };
  const str = JSON.stringify(data);
  if('sendBeacon' in window.navigator) {
    
    
    if( window.navigator.sendBeacon(url, str) ) {
    
    
      clear(collect);
    } else {
    
    
      // 异步发请求失败
    }
  } else {
    
    
    // todo 同步 ajax
    clear(collect);
  }
}

const isSafari = typeof safari === 'object' && safari.pushNotification;
const isIE10 = !('onpagehide' in window);

window.addEventListener('visibilitychange', collect, true);
!isIE10 && window.addEventListener('pagehide', collect, true);

if(isSafari || isIE10) {
    
    
  window.addEventListener('beforeunload', collect, true);
}
  • 兼容性报告
    lifecycle-events-testing

FAQs

页面不可见(hidden)时有重要的任务在执行,如何阻止页面被冻结(frozen)或废弃(discarded)?

有很多合理的理由在页面不可见(hidden)状态不冻结(frozen)页面,例如APP正在播放音乐。

因此,浏览器策略会趋于保守,只有在明确不会影响用户的时候才会放弃页面。例如以下场景不会废弃页面(除非受到设备的资源限制)。

  1. Playing audio
  2. Using WebRTC
  3. Updating the table title or favicon
  4. Showing alerts
  5. Sending push notifications

Tips:对于更新标题或favicon以提醒用户未读通知的页面,建议使用 service worker,这将允许Chrome冻结或放弃页面,但仍然显示对选项卡标题或favicon的更改。

什么是页面导航缓存(page navigation cache)?

页面导航缓存是一个通用术语,用于优化后退和前进按钮导航,利用缓存快速恢复前后页面。Webkit 称 Page CacheFirefoxBack-Forwards Cache (bfcache)。

冻结是为了节省CPU/电池/内存,而缓存是为了重载时快速恢复,两者配合才能相得益彰。因此,该缓存被视为冻结生命周期状态的一部分。

注意:beforeunloadunload 会阻止该项优化。

为什么生命周期里没有 load、DOMContentLoaded 事件?

页面生命周期状态定义为离散和互斥的。由于页面可以在activepassivehidden 状态下加载,因此单独的加载状态没有意义,
并且由于 loadDOMContentLoaded 事件不表示生命周期状态更改,因此它们与生命周期无关。

frozen 或 terminated 状态如何使用异步请求

在这两个状态,任务可能被挂起不执行,例如异步请求、基于回调的API等同样不会被执行。以下是一些建议

  1. sessionStorage,方法是同步的,且在废弃状态仍然能持久化数据。
  2. service worker,在 terminateddiscarded 状态时通过监听freeze or pagehide 通过 postMessage() 用来保存数据。(受限与设备资源,可能唤起service worker 会加重设备负担)
  3. navigator.sendBeacon 函数运行页面关闭时仍然可以发送异步请求。

如何查看页面 frozen and discarded 状态?

chrome://discards

chrome-discards

参考

  1. WebKit Page Cache
  2. Firefox Back-Forward Cache
  3. Page Lifecycle W3C
  4. Page Lifecycle API
  5. Don’t lose user and app state, use Page Visibility
  6. page-lifecycle
  7. PageLifecycle.js
  8. Lifecycle events with Page Visibility + Beacon API
  9. Why does visibilitychange fire after pagehide in the unload flow?

Guess you like

Origin blog.csdn.net/guduyibeizi/article/details/116056868