Web應用性能始終至關重要,尤其在網頁開發中,緩慢的頁面加載速度會直接導致用戶流失。作為專業的前端開發者,我們必須重視性能優化。許多傳統的網頁性能優化方法,例如減少請求次數、使用CDN以及避免編寫阻塞渲染的代碼,至今仍然有效。然而,隨著越來越多的Web應用使用JavaScript,驗證代碼效率變得至關重要。
假設您有一個功能正常的函數,但懷疑其效率不高,併計劃進行改進。如何證明這一假設?目前測試JavaScript函數性能的最佳實踐是什麼?通常,最佳方法是使用內置的performance.now()
函數,測量函數執行前後的時間。本文將討論如何測量代碼執行時間以及避免一些常見陷阱的技巧。
關鍵要點
performance.now()
函數來測量函數執行前後的時間。 now()
的函數,它返回一個DOMHighResTimeStamp
對象。這提供了對已過去時間的精確描述,使其比內置的Date
對象更精確。 performance.now()
之間唯一測量的操作是所討論函數的執行。 performance.now()
高分辨率時間API提供了一個名為now()
的函數,它返回一個DOMHighResTimeStamp
對象。這是一個浮點數,以毫秒為單位反映當前時間,精確到千分之一毫秒
。單獨來看,這個數字對您的分析並沒有多大價值,但是兩個這樣的數字之間的差值可以精確地描述已經過去了多少時間。除了比內置的Date
對象更精確之外,它也是“單調的”。這意味著,簡單來說,它不受系統(例如您的筆記本電腦操作系統)定期校正系統時間的影響。更簡單地說,定義Date
的兩個實例併計算差值並不能代表已過去的時間。 “單調”的數學定義是(對於函數或數量)以這樣一種方式變化,即它要么從不減少,要么從不增加
。另一種解釋方法是,試想一下在一年中時鐘向前或向後撥動的時間段內使用它。例如,當您所在國家的時鐘都同意為了最大限度地利用白天陽光而跳過一小時時。如果您要在時鐘撥回一小時之前創建一個Date
實例,然後在之後再創建一個Date
實例,查看差異,它會顯示類似“1小時3秒123毫秒”的內容。使用performance.now()
的兩個實例,差異將是“3秒123毫秒和456789千分之一毫秒”。在本節中,我不會詳細介紹此API。因此,如果您想了解更多信息並查看其使用的一些示例,我建議您閱讀文章《Discovering the High Resolution Time API》。既然您已經了解了高分辨率時間API是什麼以及如何使用它,讓我們深入探討一些潛在的陷阱。但在這樣做之前,讓我們定義一個名為makeHash()
的函數,我們將在本文的其餘部分使用它。
<code class="language-javascript">function makeHash(source) { var hash = 0; if (source.length === 0) return hash; for (var i = 0; i < source.length; i++) { var char = source.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; }</code>
此函數的執行可以按如下所示進行測量:
<code class="language-javascript">var t0 = performance.now(); var result = makeHash('Peter'); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);</code>
如果您在瀏覽器中運行此代碼,您應該會看到類似以下內容:
<code>Took 0.2730 milliseconds to generate: 77005292</code>
下面顯示了此代碼的實時演示:
[此處應插入CodePen鏈接,由於我無法訪問外部網站,無法提供]
考慮到這個例子,讓我們開始討論。
在上面的示例中,您可以注意到,在一次performance.now()
和另一次performance.now()
之間,我們唯一做的就是調用函數makeHash()
並將它的值賦給變量result
。這給了我們執行該函數所需的時間,而沒有其他任何內容。此測量也可以按如下所示進行:
<code class="language-javascript">var t0 = performance.now(); console.log(makeHash('Peter')); // 不好的主意! var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds');</code>
下面顯示了此代碼段的實時演示:
[此處應插入CodePen鏈接,由於我無法訪問外部網站,無法提供]
但在這種情況下,我們將測量調用函數makeHash('Peter')
所需的時間以及在控制台上發送和打印該輸出所需的時間。我們不知道這兩個操作分別需要多長時間。您只知道組合時間。此外,發送和打印輸出所需的時間會根據瀏覽器甚至當時瀏覽器中發生的情況而有很大差異。
也許您完全知道console.log
不可預測地慢。但是,執行多個函數也是同樣錯誤的,即使每個函數都不涉及任何I/O。例如:
<code class="language-javascript">function makeHash(source) { var hash = 0; if (source.length === 0) return hash; for (var i = 0; i < source.length; i++) { var char = source.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; }</code>
同樣,我們不知道執行時間是如何分配的。是變量賦值、toLowerCase()
調用還是toString()
調用?
另一個常見錯誤是只進行一次測量,總結所花費的時間,並根據該時間得出結論。在不同時間,這很可能完全不同。執行時間很大程度上取決於各種因素:
一種增量改進方法是重複執行該函數,如下所示:
<code class="language-javascript">var t0 = performance.now(); var result = makeHash('Peter'); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);</code>
下面顯示了此示例的實時演示:
[此處應插入CodePen鏈接,由於我無法訪問外部網站,無法提供]
這種方法的風險在於,我們的瀏覽器JavaScript引擎可能會進行次優化,這意味著第二次使用相同的輸入調用該函數時,它可以從記住第一個輸出中獲益,並簡單地再次使用它。為了解決這個問題,您可以使用許多不同的輸入字符串,而不是重複發送相同的輸入字符串(例如“Peter”)。顯然,使用不同輸入進行測試的問題在於,我們正在測量的函數自然會花費不同的時間。也許某些輸入會導致比其他輸入更長的執行時間。
在上一節中,我們了解到重複運行某些內容是一個好習慣,理想情況下應該使用不同的輸入。但是,我們必須記住,不同輸入的問題在於,執行時間可能比所有其他輸入都長得多。因此,讓我們退一步,發送相同的輸入。假設我們發送相同的輸入十次,並且每次都打印出花費的時間。輸出可能如下所示:
<code class="language-javascript">function makeHash(source) { var hash = 0; if (source.length === 0) return hash; for (var i = 0; i < source.length; i++) { var char = source.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32bit integer } return hash; }</code>
請注意,第一次,數字與其他九次完全不同。這很可能是因為我們瀏覽器中的JavaScript引擎進行了一些次優化,需要一些預熱。我們幾乎無法避免這種情況,但我們可以考慮一些好的補救措施來防止錯誤的結論。一種方法是計算最後九次的時間平均值。另一種更實用的方法是收集所有結果併計算中位數。基本上,這是按順序排列的所有結果,並選擇中間的一個。這就是performance.now()
如此有用的地方,因為您可以得到一個可以隨意使用的數字。讓我們再試一次,但這次我們將使用一個中位數函數:
<code class="language-javascript">var t0 = performance.now(); var result = makeHash('Peter'); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);</code>
我們已經了解到,多次測量某些內容並取平均值總是一個好主意。此外,最後一個例子告訴我們,最好使用中位數而不是平均值。現在,實際上,測量函數執行時間的一個很好的用途是了解幾個函數中哪個更快。假設我們有兩個函數,它們接受相同類型的輸入並產生相同的結果,但在內部它們的工作方式不同。假設我們想要一個函數,如果某個字符串存在於其他字符串的數組中,則返回true或false,但這不區分大小寫。換句話說,我們不能使用Array.prototype.indexOf
,因為它不區分大小寫。這是一個這樣的實現:
<code>Took 0.2730 milliseconds to generate: 77005292</code>
我們立即註意到,這可以改進,因為haystack.forEach
循環總是遍歷所有元素,即使我們有一個早期匹配。讓我們嘗試使用一個好的舊for循環來編寫一個更好的版本。
<code class="language-javascript">var t0 = performance.now(); console.log(makeHash('Peter')); // 不好的主意! var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds');</code>
現在讓我們看看哪個更快。我們通過運行每個函數10次並收集所有測量結果來做到這一點:
<code class="language-javascript">var t0 = performance.now(); var name = 'Peter'; var result = makeHash(name.toLowerCase()).toString(); var t1 = performance.now(); console.log('Took', (t1 - t0).toFixed(4), 'milliseconds to generate:', result);</code>
我們運行它並得到以下輸出:
<code class="language-javascript">var t0 = performance.now(); for (var i = 0; i < 10; i++) { makeHash('Peter'); } var t1 = performance.now(); console.log('Took', ((t1 - t0) / 10).toFixed(4), 'milliseconds to generate');</code>
下面顯示了此示例的實時演示:
[此處應插入CodePen鏈接,由於我無法訪問外部網站,無法提供]
究竟發生了什麼?第一個函數快了三倍。這本不應該發生!
解釋很簡單,但很微妙。使用haystack.forEach
的第一個函數受益於瀏覽器JavaScript引擎中的一些低級優化,而當我們使用數組索引技術時,我們不會得到這些優化。它證明了我們的觀點:除非你測量它,否則你永遠不會知道!
結論
在我們試圖演示如何使用performance.now()
在JavaScript中獲得精確的執行時間時,我們偶然發現了一個基準測試場景,在這個場景中,我們的直覺與我們的經驗結果得出的結論恰恰相反。關鍵是,如果您想編寫更快的Web應用程序,您的JavaScript代碼需要進行優化。因為計算機(幾乎)是活生生的東西,所以它們是不可預測和令人驚訝的。知道我們的代碼改進會產生更快的執行速度的最可靠方法是測量和比較。我們永遠不知道哪段代碼更快,如果我們有多種方法來做同樣的事情,另一個原因是上下文很重要。在上一節中,我們執行了一個不區分大小寫的字符串搜索,在一個26個其他字符串中查找一個字符串。如果我們必須在100,000個其他字符串中查找一個字符串,那麼結論很可能會完全不同。上面的列表並不詳盡,因為還有更多需要注意的陷阱。例如,測量不切實際的場景或只在一個JavaScript引擎上進行測量。但可以肯定的是,對於想要編寫更快、更好的Web應用程序的JavaScript開發者來說,performance.now()
是一個巨大的財富。最後但並非最不重要的一點是,請記住,測量執行時間只會產生“更好代碼”的一個維度。還需要考慮內存和代碼複雜性。您呢?您是否曾經使用此函數來測試代碼的性能?如果沒有,您在此階段如何進行?請在下面的評論中分享您的想法。讓我們開始討論吧!
(FAQs部分略去,因為與上面內容高度重複,只需保留關鍵要點即可。)
以上是測量JavaScript功能&#x27;表現的詳細內容。更多資訊請關注PHP中文網其他相關文章!