JavaScript 效能測試與最佳化
說明
本文針對一些 JavaScript 效能相關的文章之理論進行實際的測試與驗證,結果有些符合預期,有些則意料之外。測試環境瀏覽器如下:
- Chrome:18.0.1025.168 m
- Firefox:12.0
- Safari:5.1.5(7534.55.3)
- Opera:11.62
- IE8、IE7 為使用 IE9 切換模式
變數存取
JavaScript 變數存取具有以下特性:
- 使用變數時會透過作用域鏈 (Scope Chain) 自動判斷其所屬範圍,會從目前區域變數查找,逐漸往外圍查找。對變數存取屬性 (點符號.) 會進行查找而消耗時間。
- 根據以上原則,我們可以推論出一些增加效能的可能方法並實際測試:
使用區域變數,減少變數查找次數,包含作用域鏈與存取屬性,盡可能的使用區域變數。
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
存取區域變數 | 14.9 | 15.4 | 62.8 | 58.3 | 30 | 25 | 30 |
存取全域變數 | 13.8 | 17 | 73.9 | 74.1 | 43.7 | 11165.7 | 11208.6 |
存取變數屬性 | 15.3 | 12.9 | 80.8 | 74.5 | 45.1 | 40.4 | 43.1 |
存取變數屬性 (window) | 1392.3 | 1389.9 | 1458 | 1227.4 | 8354.5 | 27116.4 | 28762.2 |
執行次數 10000000,10 次平均,時間單位 ms
結果:使用區域變數效能最好,IE8 之前的版本更為明顯。
由於 JavaScript 中的函式也是一種物件,我們依據上面的邏輯將呼叫函式也進行測試。
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
呼叫區域函式 | 17.5 | 80 | 159.1 | 126.8 | 58.5 | 65.3 | 58.3 |
呼叫全域函式 | 17.4 | 20.1 | 161.1 | 68.2 | 80.5 | 74.6 | 72.9 |
呼叫變數函式 | 132 | 88.6 | 136.3 | 166.8 | 74.6 | 68.6 | 77.2 |
執行次數 10000000,10 次平均,時間單位 ms
結果:測試結果發現並不如預期,反而是呼叫全域函式的效能較佳。
相同的道理,陣列長度屬性暫存到變數中會增加效能
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
array.length | 20 | 13.3 | 60.2 | 63.2 | 44.7 | 41.9 | 41.3 |
var length | 16.2 | 11.5 | 27.6 | 23.8 | 24.5 | 22.4 | 20.6 |
執行次數 10000000,10 次平均,時間單位 ms
結果:使用區域變數效能最好。
直接宣告物件屬性,會比宣告完後再設值好,而存取方式又分為屬性和關聯式陣列兩種
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
直接宣告 | 93.6 | 177.8 | 450.1 | 407 | 899.9 | 882.5 | 900 |
變數屬性 | 769.4 | 216.7 | 441.2 | 743.3 | 2492.7 | 2466.6 | 2493.2 |
關聯式陣列 | 791.8 | 236.7 | 1448.3 | 761.1 | 2494.9 | 2492.7 | 2486.8 |
執行次數 10000000,10 次平均,時間單位 ms
結果:直接宣告效果最好,而存取又以屬性方式存取較好,在Safari最為明顯。
避免使用 with
使用 with 關鍵字時會作用域鏈會改變,增加額外的搜索時間。合併上面的表格比較
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
存取區域變數 | 14.9 | 15.4 | 62.8 | 58.3 | 30 | 25 | 30 |
存取全域變數 | 13.8 | 17 | 73.9 | 74.1 | 43.7 | 11165.7 | 11208.6 |
存取變數屬性 | 15.3 | 12.9 | 80.8 | 74.5 | 45.1 | 40.4 | 43.1 |
存取變數屬性(window) | 1392.3 | 1389.9 | 1458 | 1227.4 | 8354.5 | 27116.4 | 25280 |
with | 5949.1 | 10287.6 | 2406.9 | 1792.5 | 527.3 | 511.6 | 536.3 |
with包含迴圈 | 14070 | 14550.1 | 4823.4 | 3934.3 | 1543.4 | 1535.8 | 1553.7 |
執行次數 10000000,10 次平均,時間單位 ms
結果:使用 with 關鍵字效能大幅下降,且包含的範圍越大效能越差。
Eval is Evil
執行 eval 函式需要使用 script 引擎將字串轉換為可執行的程式,因此要避免使用 eval。
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
一般 | 24.9 | 15.3 | 49.9 | 55.2 | 33.6 | 26.3 | 31.6 |
eval | 6519.2 | 25739.1 | 2912.5 | 6055 | 2406.7 | 2189.3 | 2194.6 |
執行次數 10000000,10 次平均,時間單位 ms
結果:使用 eval 效能大幅下降。
相同的原理,建立 Function 物件,避免使用 new Function 來建立。
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
function() {} | 170.3 | 1560.4 | 370.5 | 866.7 | 888.5 | 1250 | 1148.2 |
new Function | 11730.8 | 52299.8 | 27790.5 | N/A | 7848.2 | 9579.8 | 9844.2 |
執行次數 10000000,10 次平均,時間單位 ms
結果:使用 new Function 效能大幅下降,其中Opera瀏覽器還有bug,記憶體爆炸無法跑完。
字串處理
在處理字串方面,使用不同方式來連接字串,並進行比較
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
+= | 133.9 | 40.2 | 79.2 | 99.2 | 121.3 | 99.4 | 102.8 |
string.concat | 144.2 | 45.4 | 50.8 | 128.4 | 96.8 | 108.2 | 103.2 |
array.join | 213.7 | 79.2 | 79.7 | 144.5 | 69.2 | 71.2 | 74.8 |
執行次數 1000000,10 次平均,時間單位 ms
結果:文獻資料表示使用 array.join 效果最好,然而實際上,只在 IE 瀏覽器表現如此。
上面的例子是一次接一個字串或變數,然而實際上時常是多個字串或變數,測試如下
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
+= a + b | 124.8 | 45.7 | 101.9 | 96.6 | 243.3 | 255.4 | 257.3 |
+=a +=b | 272.7 | 74.3 | 156.1 | 193.2 | 239.8 | 260.6 | 261.9 |
string.concat a + b | 266.2 | 41.4 | 162.4 | 143.1 | 300.5 | 287.8 | 277.4 |
string.concat a string.concat b | 315.2 | 195 | 154.9 | 261.3 | 269.4 | 280.1 | 263.7 |
string.concat a, b | N/A | 61.1 | 182.1 | 199.6 | 304.8 | 285.9 | 284.2 |
push a + b array.join | 364.4 | 86.7 | 258.7 | 167.8 | 235.5 | 223.2 | 228.7 |
push a push b array.join | 343.1 | 121.1 | 160.9 | 218.1 | 144.4 | 139.7 | 139.3 |
執行次數 1000000,10 次平均,時間單位 ms
結果:除了 IE 瀏覽器使用 array.join 效能較好,其他瀏覽器幾乎都是直接使用 += a + b 就有不錯的表現,結論是直接相加是最合適的。
使用原生的指令
使用最基本的程式指令會比呼叫包裝好的函式功能效能還好,呼叫函式會有額外堆疊產生,以下以 Math.max 為例
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
Math.max(a, b) | 41.7 | 38.9 | 619.2 | 799.3 | 167.9 | 169.5 | 174.8 |
a < b ? b : a | 29.4 | 50.1 | 61.1 | 54.9 | 30.7 | 31.3 | 28.6 |
執行次數 10000000,10 次平均,時間單位 ms
結果:在大部分的瀏覽器都是使用基本的程式判斷效能較好。
相同的道理,使用一些 JavaScript Framework 雖然提供許多好用的功能,但是可能會造成效能下降,以下以 jQuery 為例
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
getElementById | 116.1 | 208.4 | 126.5 | 554.9 | 849.8 | 4572.9 | 4394.4 |
jQuery(“#id”) | 1287 | 6801.5 | 2561 | 1877.7 | 2555.7 | 19403.1 | 19273.6 |
執行次數 1000000,10 次平均,時間單位 ms
結果:使用 jQuery 效能明顯下降。
Try-Catch 語句
使用 Try-Catch 時,執行次數會造成額外的耗時,在遇到迴圈時,應該建立在迴圈之外
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
迴圈外 | 65.1 | 35.2 | 63.6 | 499.5 | 40.6 | 42.3 | 39 |
迴圈內 | 74 | 42.5 | 65.8 | 551.3 | 161.9 | 154.7 | 161.1 |
執行次數 10000000,10 次平均,時間單位 ms
結果:包含的範圍在迴圈外時效果較好。
DOM處理
加入Element
使用 JavaScript 動態加入 Element 時,直接加入會造成 Reflow,所以應該先在程式中處理完成後最後在加入,例如先使用 createDocumentFragment 來存放即將加入的 Element
結果:由於這部分測試程式的完成時間和 ui 實際 reflow 完成的時間不一致,故沒有精確的時間記錄,但測試結果效能排名依序 innerHTML > createDocumentFragment > 直接加入,直接修改 innerHTML 最佳。
修改CSS
由於修改文件中的 Element 的 CSS 屬性會造成 reflow 和 repaint,當要修改多項屬性時,應該使用 className 一次性修改
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
直接修改 | 122.8 | 418.1 | 254.4 | 236.2 | 638.6 | 516.7 | 1510.2 |
cssText | 567.8 | 7904.6 | 495.6 | 57 | 522.4 | 624.8 | 1695.1 |
setAttribute(“style”, …) | 466.4 | 49.7 | 473.9 | 69.8 | 538 | 715.4 | no support |
className | 27 | 42.7 | 44.2 | 51.8 | 226.2 | 352.6 | 1489.4 |
執行次數 100000,10 次平均,時間單位 ms
結果:使用修改 className 效果最好,然而實務上屬性時常是變動值,無法預先建立 class,而不得已要修改 element 的 css 時,以 setAttribute 在大多數瀏覽器表現最好,但 IE7 不支援此方法。
承上,故有理論提出將 Element 隱藏或複製在程式中後處理,以避免 reflow
Chrome | Firefox | Safari | Opera | IE9 | IE8 | IE7 | |
---|---|---|---|---|---|---|---|
直接修改 | 101.5 | 463 | 253.8 | 227 | 632.8 | 513 | 1509.5 |
display: none | 104.6 | 457.3 | 258.7 | 230.1 | 430.9 | 517.1 | 1362 |
cloneNode | 97 | 266 | 252.1 | 229.6 | 1658.6 | 1703.9 | 891.5 |
執行次數 100000,10 次平均,時間單位 ms
結果:cloneNode 只在 Firefox 效能有提升,在 IE9 和 IE8 反而下降;而隱藏只在 IE9 有作用,總和來看,隱藏的方式效能是最平均。
變數名稱與註解
有些文章中提到,變數的名稱太長和註解會造成效能下降,然而實際上測試完全沒有影響,可以放心的使用。