前端工程師須要知道的性能知識

咱們都知道網站性能很重要。那在說到網站性能、網站速度很快的時候,咱們具體指的是什麼呢?git

首先咱們要知道,網站打開速度是一個相對的概念。同一個網站,在高端機上或者網絡優良的狀況下,能夠是很快的,在低端機,網絡差的狀況卻很慢。github

下圖是一個網站的打開時間分佈圖,X 軸是耗時區間,Y 軸是用戶量。首先從這個圖中,咱們能夠知道,網站打開速度並非一個固定的數值,並且一個區分分佈。雖然大部分用戶打開時間在 2s 內,但其實仍是存在少許用戶,他們的打開時間大於 10 秒。web

FMP區間分佈圖

還有一些其餘的概念:canvas

  • 兩個網站加載時間相同,漸進式渲染的網站比加載完渲染的網站,感受上會更快。
  • 網站加載速度很快,交互響應很慢,咱們也會以爲網站性能差。

定義指標

那麼咱們該若是衡量網站性能呢,站在用戶的角度上想,咱們須要回答一下幾個問題?瀏覽器

是否發生? 導航發生了嗎?服務器響應了嗎?
是否有用? 頁面是否渲染出足夠可用內容
是否可用? 用戶能夠與該頁面進行交互,仍是仍在加載中?
是否使人愉快? 交互是否順暢天然,沒有滯後和卡頓?

如何度量

先用一個直觀的圖片來展現,頁面顯示的各個階段。緩存

Page Painting

是否發生 FP 、 FCP

FP (First Paint) 首次繪製,表明瀏覽器第一次像屏幕傳輸像素的時間,也就是頁面在屏幕上首次發生視覺變化的時間。安全

FCP (First Contentful Paint) 首次內容繪製,表明瀏覽器第一次繪製內容的時間。服務器

如何計算markdown

function showPaintTimings() {
  if (window.performance) {
    let performance = window.performance;
    let performanceEntries = performance.getEntriesByType('paint');
    performanceEntries.forEach((performanceEntry, i, entries) => {
      console.log(
        'The time to ' +
          performanceEntry.name +
          ' was ' +
          performanceEntry.startTime +
          ' milliseconds.'
      );
    });
  } else {
    console.log("Performance timing isn't supported.");
  }
}

showPaintTimings();
// The time to first-paint was 949.5900000038091 milliseconds.
// The time to first-contentful-paint was 949.5900000038091 milliseconds.
複製代碼

FP 不包含默認背景繪製,可是包含非默認背景繪製。網絡

只有繪製文本、圖片(包含背景圖)、非白色的 svg 或者 canvas 是才被算做 FCP

是否有用 FMP

FMP (First Meaningful Paint) 首次有效繪製,是頁面主要內容繪製的時間點。

如何計算 FMP 指標

在打開一個網頁的時候,隨着網頁的加載與解析,瀏覽器會將佈局對象(Layout Object)逐步添加到佈局樹(Layout Tree)上進行佈局。

example1

example2

  • 圖 1 展現了當加載谷歌搜索結果頁面時,被逐步加載到佈局樹中的佈局對象的數量。
  • 圖 2 展現了加載「谷歌搜索結果頁」在加載和渲染過程當中的可視化過程

將兩張圖結合起來解讀

  1. 1.577s,頁面頭部渲染,」佈局對象「總數是 60 個。
  2. 1.76s,頁面頭部渲染完成,「佈局對象」總數是 103 個。
  3. 1.907s, 搜索結果數據返回並渲染,「佈局對象」總數是 261。而此時頁面主體內容已經繪製完成,從用戶體驗的角度看,此時的時間點就是是 FMP。
  4. 2.425s, 其餘搜索結果和頁面底部的佈局對象繼續被添加到佈局樹中並進行繪製,頁面完成最終加載和渲染。

從以上對於「谷歌搜索結果頁」加載過程的例子中能夠發現,佈局對象的數量與頁面完成度高度相關。咱們得出如下結論:

FMP = 頁面在加載和渲染過程當中最大布局變更以後的那個繪製時間
複製代碼

代碼實現

基於剛剛得出的結論:FMP 的時間點爲 DOM 結構變化最劇烈的時間點。DOM 結構變化的時間點能夠經過 MutationObserver API 來得到。

// 用於存放每次 dom 變話時,時間和 dom 數量
const list = [];
// 起止時間
const startTime = Date.now();
// 建立監聽
const observer = new MutationObserver(callback);
// 監聽 dom 變動
observer.observe(document, {
  childList: true,
  subtree: true,
});

// dom 變動回調
function callback() {
  const duration = Date.now() - startTime;
  const body = document.querySelector('body');
  list.push({
      number: body ? count(body) : 0,
      duration,
    });
}

// 計算 dom 數量
function count(element) {
  let number = 0;
  const childrenLength = element.children ? element.children.length : 0;
  if (childrenLength > 0) {
    const children = element.children;
    for (let length = childrenLength - 1; length >= 0; length--) {
      number += count(children[length]);
    }
  }
  number += 1;
  return number;
}

// 找出變化作大的時間點, 計作 FMP
function getFmp() {
  let result;
  for (let i = 1; i < list.length; i++) {
    const diff = list[i].number - list[i - 1].number;
    if (!result.diff || diff > result.diff) {
      result = {
        duration: list[i].duration,
        diff,
      };
    }
  }
  return result?.duration || 0;
}
複製代碼

優化實現

當前方法能夠在大部分狀況下得出 FMP 值,可是在一些其餘場景下,仍是存在誤差。

example3

example4

圖 3 是微博頁面在加載和渲染的可視化過程

圖 4 展現了當加載微博頁面時,被逐步加載到佈局樹中的佈局對象的數量。

經過圖 3 圖 4 能夠看出,主要元素加載時間在 6.047 秒,可是佈局對象發生變化最大的時候,是 24.25 秒。在 24.25 秒的時候,頁面底部到可見區域外,大量元素被添加到佈局樹上。

爲了優化上述現象,咱們引入佈局意義概念

佈局意義 = 添加的佈局對象數量 / max(1, 頁面高度 / 屏幕高度)
複製代碼

代碼優化

// 計算頁面比率
function getRatio() {
  const clinetH = document.body.clientHeight;
  const screenH = window.screen.availHeight;
  return Math.max(clinetH / screenH, 1);
}

// 更新 callback 方法
function callback() {
  const duration = Date.now() - startTime;
  const body = document.querySelector('body');
  list.push({
      number: body ? count(body) / getRatio(): 0,
      duration,
    });
}
複製代碼

圖 5 展現了當加載微博頁面時,佈局意義變化狀況。佈局意義最大變化發生在 5.89 秒,在 FMP(6.047)秒以前,符合預期。

是否可用 TTI

TTI (Time to Interactive) 可交互時間,表明網頁第一次達到可交互的時間點。首頁是頁面的 ui 是可交互的狀態,而且無場長任務運行,即頁面是流暢的。

下圖演示瞭如何查找 TTI。

TTI

  • 從 FCP 開始,向前搜索一個 5s 以上的靜默窗口。靜默窗口:無場任務,且網絡請求不超過 2 個。
  • 從靜默窗口日後搜索,最後一個長任務的結束時間,若是搜索不到,則在 FCP 處中止。
  • TTI 是靜默窗口以前的最後一個長任務的結束時間(若是找不到長任務,則使用 FCP 的值做爲 TTI)。

如何計算

谷歌團隊開發了一個polyfill,用於檢測 TTI,適用於全部支持 Long Tasks API 的瀏覽器。

import ttiPolyfill from './path/to/tti-polyfill.js';

ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {
  // Use `tti` value in some way.
});
複製代碼

長任務(Long Tasks API):任何連續不間斷的且主 UI 線程繁忙 50 毫秒及以上的時間區間。

是否使人愉快 長任務監聽

頁面」是否使人愉快「,主要有幾個角度,動畫是否流暢,用戶交互是否能夠快速影響。而卡頓、交互響應慢的狀況一般由長任務致使的,瞭解長任務的發生頻率,能夠幫助咱們判斷頁面是否流暢。

如何計算

var observer = new PerformanceObserver(function(list) {
  var perfEntries = list.getEntries();
  for (var i = 0; i < perfEntries.length; i++) {
    // Process long task notifications:
    // report back for analytics and monitoring
    // ...
  }
});
// register observer for long task notifications
observer.observe({ entryTypes: ['longtask'] });
// Long script execution after this will result in queueing
// and receiving "longtask" entries in the observer.
複製代碼

其餘性能指標

下圖1、圖二是 Navigation Timing API 初版和第二版,該 API 提供了可用於衡量一個網站性能的數據。記錄了頁面重定向、DNS 查詢、TCP、SSL 鏈接、內容請求、DOM 解析等的時間點。

Navigation Timing Level 1

Navigation Timing Level 2

指標解讀

指標 說明
startTime 0
unloadEventStart 上一個頁面 unload 事件的觸發時間,只有同源跳轉能夠記錄,非同源返回 0
unloadEventEnd 上一個頁面 unload 事件的結束時間
redirectStart 第一個 http 重定向請求發起時間,只有同源跳轉能夠記錄,非同源返回 0
redirectEnd 最後一個 http 重定向請求發起時間
fetchStart 請求開始時間,發生在檢查本地緩存以前
domainLookupStart 域名解析(DNS)開始時間,若是存在本地緩存,則該值等同於 fetchStart
domainLookupEnd 域名解析(DNS)結束時間,若是存在本地緩存,則該值等同於 fetchStart
connectStart 請求鏈接被髮送到網絡的時間
secureConnectionStart 安全鏈接握手開始的時間
connectEnd 網絡連接創建的時間
requestStart 瀏覽器發送文檔請求的開始時間
responseStart 瀏覽器接收到響應的第一個字節的時間
responseEnd 瀏覽器接收到響應的最後一個字節的時間
domInteractive 主文檔的解析器結束工做,Document.readyState 改變爲 interactive 的時間,至關於 readystatechange 時間觸發的時間
domContentLoadedEventStart 全部的須要被運行的腳本已經被解析,即 DOMContentLoaded 事件觸發時間
domContentLoadedEventEnd 全部的須要被運行的腳本已經執行完畢
domComplete 主文檔的解析器結束工做,Document.readyState 變爲 complete
loadEventStart load 事件觸發時間
loadEventEnd load 時間完成時間

階段指標

階段 計算方式
DNS 查詢 domainLookupEnd - domainLookupStart
TCP 鏈接 connectEnd - connectStart
SSL 建連 connectEnd - secureConnectionStart
首字節網絡請求(TTFB) responseStart - requestStart
內容傳輸 responseEnd - responseStart
DOM 解析 domInteractive - responseEnd
白屏時間 domInteractive - t.fetchStart
資源加載 loadEventStart - domContentLoadedEventEnd
DOM Ready domContentLoadedEventEnd - fetchStart

相關文章

User-centric performance metrics

First Contentful Paint

Time to First Meaningful Paint

Time to Interactive (TTI)

渲染頁面:瀏覽器的工做原理

相關文章
相關標籤/搜索