譯者:前端小智
做者:Felix Gerschau
來源:felixgerschau
點贊再看,微信搜索
【大遷世界】 關注這個沒有大廠背景,但有着一股向上積極心態人。本文
GitHub
https://github.com/qq44924588... 上已經收錄,文章的已分類,也整理了不少個人文檔,和教程資料。
要比較兩個函數哪一個性能更好,一個直觀且公平的方法就是計算兩個函數分別執行完的時間。前端
良好的性能更容易好的用戶體驗,而好的用戶體驗更能留住用戶。 研究代表,因爲性能問題,在88%
的在線消費者對用戶體驗不滿意後,他們不太可能會二次使用。java
這也是爲何要提升性能的一個重要緣由。 特別是使用 JS 開發時,編寫的每一行 JS 均可能會阻塞DOM,由於它是單線程語言。git
本次分享,咱們主要介紹如何計算函數的性能。github
Performance
是一個作前端性能監控離不開的API,最好在頁面徹底加載完成以後再使用,由於不少值必須在頁面徹底加載以後才能獲得。最簡單的辦法是在window.onload
事件中讀取各類數據。api
performance.now()
方法返回一個精確到毫秒的 DOMHighResTimeStamp
。數組
根據 MDN :瀏覽器
這個時間戳實際上並非高精度的。爲了下降像Spectre這樣的安全威脅,各種瀏覽器對該類型的值作了不一樣程度上的四捨五入處理。(Firefox從Firefox 59開始四捨五入到2毫秒精度)一些瀏覽器還可能對這個值做稍微的隨機化處理。這個值的精度在將來的版本中可能會再次改善;瀏覽器開發者還在調查這些時間測定攻擊和如何更好的緩解這些攻擊。
由於,要計算一個函數的執行時間,分別比較函數執行前和執行後的兩次 performance.now()
的值便可,以下所示:緩存
const t0 = performance.now(); for (let i = 0; i < array.length; i++) { // some code } const t1 = performance.now(); console.log(t1 - t0, 'milliseconds');
在這裏,咱們能夠看到 Firefox 中的結果與 Chrome 徹底不一樣。 這是由於從版本60
開始,Firefox 將performance API的精度下降到2ms
。安全
performance API 不當當只有返回時間戳這個功能,還有不少實用方法,你們能夠根據須要到 MDN 查詢相關的文檔。性能優化
然而,對於咱們的用例,咱們只想計算單個函數的性能,所以時間戳就足夠了。
performance.now() 和 Date.now同樣嗎?
你可能會想,嘿,我也可使用Date.now
來作?
是的,你能夠,但這有缺點。
Date.now
返回自Unix紀元(1970-01-01T00:00:00Z)以來通過的時間(以毫秒爲單位),並取決於系統時鐘。 這不只意味着它不夠精確,並且還不老是遞增。 WebKit工程師(Tony Gentilcore)的解釋以下:
基於系統時間的日期可能不太會被採用,對於實際的用戶監視也不是理想的選擇。 大多數系統運行一個守護程序,該守護程序按期同步時間。 一般每15至20分鐘將時鐘調整幾毫秒。 以該速率,大約10秒間隔的1%將是不許確的。
除了Performance.now
函數外,還有一些函數可讓咱們度量代碼不一樣部分的時間,並將它們做爲性能測試工具(如Webpagetest
)中的自定義度量。
先來看看MDN中關於mark方法的定義:
The mark() method creates a timestamp in the browser's performance entry buffer with the given name.
這段話能夠分解出三個關鍵詞。首先timestamp
,這裏的timestamp
指的是高精度時間戳(千分之一毫秒),其次是performance entry buffer。
performance entry buffer指的是存儲performance
實例對象的區域,初始值爲空。
最後就是given name,表示生成的每個timestamp
都有相應的名稱。
因此這句話就能夠理解成,在瀏覽器的performance entry buffer中,根據名稱生成高精度時間戳。也就是不少人說過的「打點」。
就像Performance.no
w同樣,此函數的精度分數高達5µs
。
performance.mark('name');
標記 的 performance entry將具備如下屬性值:
entryType
- 設置爲 "mark".name
- 設置爲mark被建立時給出的 "name"startTime
- 設置爲 mark()
方法被調用時的 timestamp 。duration
- 設置爲 "0" (標記沒有持續時間).一樣先來看看 MDN 上關於 measure 的定義:
這段定義和上面 mark
的定義有些相似,其最核心的不一樣點在於這句話 between two specified marks
。因此measure是指定兩個mark
點之間的時間戳。若是說mark
能夠理解爲"打點"的話,measure
就能夠理解爲"連線"。
performance.measure(name, startMark, endMark);
計算兩個mark之間的時長,建立一個DOMHighResTimeStamp
保存在資源緩存數據中,可經過performance.getEntries()
等相關接口獲取。
entryType
爲字符串 measure
name
爲建立時設置的值startTime
爲調用 measure 時的時間duration
爲兩個 mark 之間的時長從導航開始測量
performance.measure('measure name');
導航開始到標記
performance.measure('measure name', undefined, 'mark-2');
從標記到標記
performance.measure('measure name', 'mark-1', 'mark-2');
在上面的函數中,老是提到結果存儲在performance entry buffer
,可是如何訪問其中的內容呢?
performance API有3個函數能夠用來訪問該數據:
performance.getEntries()
獲取一組當前頁面已經加載的資源PerformanceEntry對象。接收一個可選的參數options
進行過濾,options
支持的屬性有name
,entryType
,initiatorType
。
let entries = window.performance.getEntries();
performance.getEntriesByName
根據參數name
,type
獲取一組當前頁面已經加載的資源數據。name
的取值對應到資源數據中的name
字段,type
取值對應到資源數據中的entryType
字段。
let entries = window.performance.getEntriesByName(name, type);
performance.getEntriesByType
根據參數type
獲取一組當前頁面已經加載的資源數據。type
取值對應到資源數據中的entryType
字段。
var entries = window.performance.getEntriesByType(type);
結合事例:
performance.mark('mark-1'); // some code performance.mark('mark-2') performance.measure('test', 'mark-1', 'mark-2') console.log(performance.getEntriesByName('test')[0].duration);
這個 API確實易於使用。當須要統計一段代碼的執行時間時,可使用console.time
方法與console.timeEn
d方法,其中console.time
方法用於標記開始時間,console.timeEnd
方法用於標記結束時間,而且將結束時間與開始時間之間通過的毫秒數在控制檯中輸出。這兩個方法的使用方法以下所示。
console.time('test'); for (let i = 0; i < array.length; i++) { // some code } console.timeEnd('test');
輸出的結果與Performance API很是類似。
console.time
的優勢是易於使用,由於它不須要手動計算兩個時間戳之間的差。
若是在不一樣的瀏覽器中使用上面提到的 api 測量函數,你可能會注意到結果是不一樣的。
這是因爲瀏覽器試圖保護用戶免受時序攻擊(timing attack)和指紋採集(Fingerprinting ),若是時間戳過於準確,黑客可使用它們來識別用戶。
例如,Firefox等瀏覽器試圖經過將精度下降到2ms(版本60)來防止這種狀況發生。
如今,咱們已經知道了要測量JavaScript函數的速度所需方法。 可是,最好還要避免一些陷阱:
開發過程當中,咱們可能會我發現有些模塊執行速度很慢,可是咱們不知道具體問題出在哪裏。解決一個方法是,使用上面提到的這些函數來測量它,而不是胡亂猜想代碼的哪一部分比較慢。
要對其進行跟蹤,首先將console.time
語句放在執行比較慢的代碼塊周圍。 而後測量它們不一樣部分的表現。 若是一個比另外一個慢,那就繼續往下走,直到發現問題所在。
在實際應用中,給定函數的輸入值可能會發生很大變化。 僅針對任意隨機值測量函數的速度並不能提供咱們能夠實際使用的任何有價值的數據。
確保使用相同的輸入值運行代碼。
假設你有一個函數,它的功是遍歷一個數組,對數組的每一個值進行一些計算,而後返回一個帶有結果的數組。你想知道是forEach
循環仍是簡單的for
循環性能更好。
function testForEach(x) { console.time('test-forEach'); const res = []; x.forEach((value, index) => { res.push(value / 1.2 * 0.1); }); console.timeEnd('test-forEach') return res; } function testFor(x) { console.time('test-for'); const res = []; for (let i = 0; i < x.length; i ++) { res.push(x[i] / 1.2 * 0.1); } console.timeEnd('test-for') return res; }
而後這樣測試它們:
const x = new Array(100000).fill(Math.random()); testForEach(x); testFor(x);
若是在 Firefox 中運行上述函數,結果:
看起來forEach
慢多了,對吧?
那若是是相同的輸入,運行兩次呢:
testForEach(x); testForEach(x); testFor(x); testFor(x);
若是咱們第二次調用forEach
測試,它的執行效果和for
循環同樣好。考慮到初始值較慢,在一些性能要求極高的項目,可能就不適合使用forEach
。
若是咱們在Chrome中運行上述代碼,結果又會不同:
這是由於Chrome和Firefox具備不一樣的JavaScript引擎,它們具備不一樣類型的性能優化。
在本例中,Firefox 在對相同輸入的forEach
進行優化方面作得更好。
for
在兩個引擎上的性能都更好,所以在一些性能要求極高的項目就須要使用for
循環。
這是爲何要在多個引擎中進行測量的一個很好的例子。 若是僅使用Chrome
進行測量,你可能會得出結論,與for
相比,forEach
並不那麼糟糕。
咱們在本地測試值是不能表明用戶在瀏覽器使用的狀況,由於 咱們開發的電腦通常都會比大部分的用戶好不少。
瀏覽器有一個特性能夠限制CPU性能,咱們經過設置能夠更貼切一些真實狀況。
在本文中,咱們看到了一些JavaScript API,咱們可使用它們來衡量性能,以及如何在真實的項目中使用它們。 對於簡單的測量,我發現使用console.time
更容易。 若是要將測量與性能測量工具集成在一塊兒,則可能須要使用performance.mark
和performance.measure
。
人才們的 【三連】 就是小智不斷分享的最大動力,若是本篇博客有任何錯誤和建議,歡迎人才們留言,最後,謝謝你們的觀看。
編輯中可能存在的bug無法實時知道,過後爲了解決這些bug,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug。
原文:https://felixgerschau.com/mea...
文章每週持續更新,能夠微信搜索 【大遷世界 】 第一時間閱讀,回覆 【福利】 有多份前端視頻等着你,本文 GitHub https://github.com/qq449245884/xiaozhi 已經收錄,歡迎Star。