前端性能優化之自定義性能指標及上報方法詳解


轉自公眾號文章,覺得不錯,拿來收藏:https://mp.weixin.qq.com/s?__biz=MzAwNDcyNjI3OA==&mid=2650843337&idx=2&sn=681c3fcbc5fdb56bbf5978921e7e5de5&chksm=80d383a0b7a40ab68f571fcd5830c69fc3d3d0ac4c9a4482cf2582a0031ce8e366e0f5215f45&mpshare=1&scene=23&srcid=0117luqxTMWcfbQiO5OgNNN3&sharer_sharetime=1579270274111&sharer_shareid=49e276b2e624f54fc5b024238a201770#rd

####### 背景

性能優化是所有前端人的追求,在這條路上,方法多種多樣。這篇文章,說一下利用瀏覽器的一些API,可以怎樣進行自定義性能指標及上報。

PS:后面會出一篇利用chrome開發者工具進行性能分析的哦,敬請期待~

####### 自定義性能指標介紹

自定義性能指標這里,主要要介紹的是 Performance 接口,這個接口可以獲取到當前頁面中與性能相關的信息。主要包含了Performance Timeline API、Navigation Timing API、 User Timing API 和 Resource Timing API。

Performance類型的對象可以通過調用只讀屬性 window.performance 來獲得,截止目前,其支持度已經很高了,支持性如下:

####### performance.now()

在chrome瀏覽器中返回的時間是以毫秒為單位的,更精確。

performance.now() 與 Date.now() 不同的是,返回了以微秒(百萬分之一秒)為單位的時間,更加精准。

並且與 Date.now() 會受系統程序執行阻塞的影響不同,performance.now() 的時間是以恆定速率遞增的,不受系統時間的影響(系統時間可被人為或軟件調整)。

這里主要是一些需要入侵業務代碼打點的時候,可以使用這個 API 來獲取時間戳

注意:Date.now() 輸出的是距離 1970 的毫秒數,而 performance.now() 輸出的是相對於 performance.timing.navigationStart(頁面初始化) 的時間。

使用 Date.now() 的差值並非絕對精確,因為計算時間時受系統限制(可能阻塞)。但使用 performance.now() 的差值,並不影響我們計算程序執行的精確時間。

####### window.performance.navigation

window.performance.navigation 對象提供了在指定的時間段里發生的操作相關信息,包括頁面是加載還是刷新、發生了多少次重定向等。我們可以看看:

其中,type 的取值及含義如下表:

具體數據示例:

這個數據,主要是幫助我們看看頁面重定向次數是否過多(能否減少重定向),頁面訪問的方式主要是怎樣的,針對訪問方式較多的場景我們能否做些優化。

這種情況下,我們只需要把數據直接上報,然后自己查看數據的時候,再跟具體含義結合起來理解即可。

####### window.performance.timing

window.performance.timing里面有很多的性能相關的時間戳記錄,我們來看一些常用的:

更多查看:https://developer.mozilla.org/en-US/docs/Web/API/PerformanceTiming

####### 關鍵指標
這樣,我們就可以定出一些關鍵步驟耗時:

這個數據,上報到數據平台系統。就可以看到頁面的性能情況如何,然后進行對應的優化了。不過,實際情況中,前端更關注的性能指標在首屏,比如:

  • HTML 加載完成時間
  • 首屏圖片加載完成時間
  • 首屏接口完成加載完成時間
代碼實現

這個時候,我們可以自己手動加上一些時間點(這里的手動添加的點都推薦使用performance.now來實現),結合一起上報。代碼示例如下:

//window.loadHtmlTime 在html中的</body>標簽前面用打個時間戳即可
HTMLComplete = window.loadHtmlTime - window.performance.timing.navigationStart

//window.lastImgLoadTime 在首屏中的每張圖onload之后都更新一次這個時間戳
firstScreenImgFinished = window.lastImgLoadTime - window.performance.timing.navigationStart

//Report.SPEED.MAINCGI 在首屏中的每個接口調用成功后更新時間戳
firstScreenApiFinished = Report.SPEED.MAINCGI - window.performance.timing.navigationStart

//在所有接口打時間點
apiFinishes = Report.SPEED.LASTCGI - window.performance.timing.navigationStart);

注意:

我們在做性能埋點的時候,最好不要入侵業務代碼。這里我的想法是,每個api調用的方法,我們都返回一個Promise,這樣,我們再另外封裝一個sdk去找到這些方法,然后分別注冊then方法來計時即可。

window.performance.getEntries()

window.performance.getEntries 是一個方法,方法調用后可以獲取一個包含了頁面中所有的 HTTP 請求的時間數據的數組.這個數組是一個按startTime排序的對象數組,數組成員除了會自動根據所請求資源的變化而改變以外,還可以用mark(),measure()方法自定義添加。

其與 performance.timing 對比的差別就是沒有與 DOM 相關的屬性。而要注意的是, HTTP 請求有可能命中本地緩存,這種情況下請求響應的間隔將非常短,數據可能不准確。

我們來看看它都包含來了哪些時間,如下一個例子圖:

由圖可以看出,每個對象的屬性中除了包含資源加載過程各個階段的時間外,還有以下五個屬性:

  • name:資源名稱,是資源的絕對路徑或調用mark方法自定義的名稱
  • startTime:開始時間
  • duration:加載時間
  • entryType:資源類型,entryType類型不同數組中的對象結構也不同
    * initiatorType:發起的請求者
    其中,常用entryType的值含義如下:

其中,常見initiatorType的值含義如下:

關鍵指標
由以上,我們可以得出一些,我們比較關心的性能指標如下:

  • 首屏圖片完成時間
  • 各資源耗時(主要統計css/js資源耗時)
  • FP(首次繪制時間)
  • FCP(首次內容渲染時間)
代碼實現

當我們調用這個方法的時候,我們得到的是調用方法前的所有資源的數據,一些資源可能有延時,或者在一些特殊的邏輯下才加載,這種情況下,我們就需要輪詢上報了。

但是,瀏覽器考慮到這些復雜的情況,它為了我們提供了一個 PerformanceObserver,用於監測性能度量事件,在瀏覽器的性能時間軸記錄下一個新的 performance entries 的時候將會被通知.

簡單來說,我們可以利用 PerformanceObserver 做到當有性能數據產生時,主動通知你(觀察者模式),所以我們監聽自己需要的資源類型,當有這個資源的時候進行上報即可。如下代碼:

function perf_observer(list, observer) {
   // Process the "measure" and "resource" event
    list
    .getEntries()
    .map(({ name, entryType, startTime, duration }) => {
      const perfObj = {
        "Duration": duration,
        "Entry Type": entryType,
        "Name": name,
        "Start Time": startTime,
      };
      returnJSON.stringify(obj, null, 2);
    })
    .forEach(console.log); // 可以加上報邏輯
}
var observer2 = new PerformanceObserver(perf_observer);
observer2.observe({entryTypes: ["paint","resource"]});

這里,關於首屏圖片的獲取和統計邏輯,我這里貼一個我使用的通用的邏輯,大家可以參考一下:

// 可以在
const getFirstScreenImageLoadTime = () => {
    // 獲取所有的 img dom 節點
    const images = document.getElementsByTagName('img');
    const imageEntries = performance.getEntries().filter(function (entry) {
        return entry.initiatorType === 'img'
    });

    // 獲取在首屏內的 img dom 節點
    const firstScreenEntry = [];
    for (let i = 0; i < images.length; i++) {
        const image = images[i];
        const ret = image.getBoundingClientRect();
        if (ret.top < (window.innerHeight - 2) && ret.right > 0 && ret.left < (window.innerWidth - 2)) {
            // 如果在首屏內
            const imageEntry = imageEntries.filter(function (entry) {
                return entry.name === image.src;
            })[0];
            imageEntry && firstScreenEntry.push(imageEntry);
        }
    }

    // 獲取最晚加載完成的一張
    let maxEntry;
    if (firstScreenEntry.length >= 1) {
        maxEntry = firstScreenEntry.reduce(function (prev, curr) {
            if (curr.responseEnd > prev.responseEnd) {
                return curr;
            } else {
                return prev
            }
        });
    }

    return maxEntry && maxEntry.responseEnd || null;
}

注意:

該方法有個草案,可以直接傳入過濾參數,然后得到想要的結果,如window.performance.getEntries(PerformanceEntryFilterOptions),但是我試了一下,目前 chrome 都還不支持,可以參考這里:https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/getEntries。

總結

最后總結一下,我們自定義的前端比較關心的性能指標大概有:

  • 白屏時間
  • HTML 加載完成時間
  • 首屏圖片加載完成時間
  • 首屏接口完成加載完成時間
  • 各資源耗時(主要統計css/js資源耗時)
  • FP(首次繪制時間)
  • FCP(首次內容渲染時間)
  • onload時間
    而這些指標的統計方法,本文都詳細寫了。可以參考一下實現方案~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM