說明

本文針對一些 JavaScript 效能相關的文章之理論進行實際的測試與驗證,結果有些符合預期,有些則意料之外。測試環境瀏覽器如下:

  • Chrome:18.0.1025.168 m
  • Firefox:12.0
  • Safari:5.1.5(7534.55.3)
  • Opera:11.62
  • IE8、IE7 為使用 IE9 切換模式

變數存取

JavaScript 變數存取具有以下特性:

  1. 使用變數時會透過作用域鏈 (Scope Chain) 自動判斷其所屬範圍,會從目前區域變數查找,逐漸往外圍查找。對變數存取屬性 (點符號.) 會進行查找而消耗時間。
  2. 根據以上原則,我們可以推論出一些增加效能的可能方法並實際測試:

使用區域變數,減少變數查找次數,包含作用域鏈與存取屬性,盡可能的使用區域變數。

ChromeFirefoxSafariOperaIE9IE8IE7
存取區域變數14.915.462.858.3302530
存取全域變數13.81773.974.143.711165.711208.6
存取變數屬性15.312.980.874.545.140.443.1
存取變數屬性 (window)1392.31389.914581227.48354.527116.428762.2

執行次數 10000000,10 次平均,時間單位 ms
結果:使用區域變數效能最好,IE8 之前的版本更為明顯。

由於 JavaScript 中的函式也是一種物件,我們依據上面的邏輯將呼叫函式也進行測試。

ChromeFirefoxSafariOperaIE9IE8IE7
呼叫區域函式17.580159.1126.858.565.358.3
呼叫全域函式17.420.1161.168.280.574.672.9
呼叫變數函式13288.6136.3166.874.668.677.2

執行次數 10000000,10 次平均,時間單位 ms
結果:測試結果發現並不如預期,反而是呼叫全域函式的效能較佳。

相同的道理,陣列長度屬性暫存到變數中會增加效能

ChromeFirefoxSafariOperaIE9IE8IE7
array.length2013.360.263.244.741.941.3
var length16.211.527.623.824.522.420.6

執行次數 10000000,10 次平均,時間單位 ms
結果:使用區域變數效能最好。

直接宣告物件屬性,會比宣告完後再設值好,而存取方式又分為屬性和關聯式陣列兩種

ChromeFirefoxSafariOperaIE9IE8IE7
直接宣告93.6177.8450.1407899.9882.5900
變數屬性769.4216.7441.2743.32492.72466.62493.2
關聯式陣列791.8236.71448.3761.12494.92492.72486.8

執行次數 10000000,10 次平均,時間單位 ms
結果:直接宣告效果最好,而存取又以屬性方式存取較好,在Safari最為明顯。

避免使用 with

使用 with 關鍵字時會作用域鏈會改變,增加額外的搜索時間。合併上面的表格比較

ChromeFirefoxSafariOperaIE9IE8IE7
存取區域變數14.915.462.858.3302530
存取全域變數13.81773.974.143.711165.711208.6
存取變數屬性15.312.980.874.545.140.443.1
存取變數屬性(window)1392.31389.914581227.48354.527116.425280
with5949.110287.62406.91792.5527.3511.6536.3
with包含迴圈1407014550.14823.43934.31543.41535.81553.7

執行次數 10000000,10 次平均,時間單位 ms
結果:使用 with 關鍵字效能大幅下降,且包含的範圍越大效能越差。

Eval is Evil

執行 eval 函式需要使用 script 引擎將字串轉換為可執行的程式,因此要避免使用 eval。

ChromeFirefoxSafariOperaIE9IE8IE7
一般24.915.349.955.233.626.331.6
eval6519.225739.12912.560552406.72189.32194.6

執行次數 10000000,10 次平均,時間單位 ms
結果:使用 eval 效能大幅下降。

相同的原理,建立 Function 物件,避免使用 new Function 來建立。

ChromeFirefoxSafariOperaIE9IE8IE7
function() {}170.31560.4370.5866.7888.512501148.2
new Function11730.852299.827790.5N/A7848.29579.89844.2

執行次數 10000000,10 次平均,時間單位 ms
結果:使用 new Function 效能大幅下降,其中Opera瀏覽器還有bug,記憶體爆炸無法跑完。

字串處理

在處理字串方面,使用不同方式來連接字串,並進行比較

ChromeFirefoxSafariOperaIE9IE8IE7
+=133.940.279.299.2121.399.4102.8
string.concat144.245.450.8128.496.8108.2103.2
array.join213.779.279.7144.569.271.274.8

執行次數 1000000,10 次平均,時間單位 ms
結果:文獻資料表示使用 array.join 效果最好,然而實際上,只在 IE 瀏覽器表現如此。

上面的例子是一次接一個字串或變數,然而實際上時常是多個字串或變數,測試如下

ChromeFirefoxSafariOperaIE9IE8IE7
+= a + b124.845.7101.996.6243.3255.4257.3
+=a
+=b
272.774.3156.1193.2239.8260.6261.9
string.concat a + b266.241.4162.4143.1300.5287.8277.4
string.concat a
string.concat b
315.2195154.9261.3269.4280.1263.7
string.concat a, bN/A61.1182.1199.6304.8285.9284.2
push a + b
array.join
364.486.7258.7167.8235.5223.2228.7
push a
push b
array.join
343.1121.1160.9218.1144.4139.7139.3

執行次數 1000000,10 次平均,時間單位 ms
結果:除了 IE 瀏覽器使用 array.join 效能較好,其他瀏覽器幾乎都是直接使用 += a + b 就有不錯的表現,結論是直接相加是最合適的。

使用原生的指令

使用最基本的程式指令會比呼叫包裝好的函式功能效能還好,呼叫函式會有額外堆疊產生,以下以 Math.max 為例

ChromeFirefoxSafariOperaIE9IE8IE7
Math.max(a, b)41.738.9619.2799.3167.9169.5174.8
a < b ? b : a29.450.161.154.930.731.328.6

執行次數 10000000,10 次平均,時間單位 ms
結果:在大部分的瀏覽器都是使用基本的程式判斷效能較好。

相同的道理,使用一些 JavaScript Framework 雖然提供許多好用的功能,但是可能會造成效能下降,以下以 jQuery 為例

ChromeFirefoxSafariOperaIE9IE8IE7
getElementById116.1208.4126.5554.9849.84572.94394.4
jQuery(“#id”)12876801.525611877.72555.719403.119273.6

執行次數 1000000,10 次平均,時間單位 ms
結果:使用 jQuery 效能明顯下降。

Try-Catch 語句

使用 Try-Catch 時,執行次數會造成額外的耗時,在遇到迴圈時,應該建立在迴圈之外

ChromeFirefoxSafariOperaIE9IE8IE7
迴圈外65.135.263.6499.540.642.339
迴圈內7442.565.8551.3161.9154.7161.1

執行次數 10000000,10 次平均,時間單位 ms
結果:包含的範圍在迴圈外時效果較好。

DOM處理

加入Element

使用 JavaScript 動態加入 Element 時,直接加入會造成 Reflow,所以應該先在程式中處理完成後最後在加入,例如先使用 createDocumentFragment 來存放即將加入的 Element

結果:由於這部分測試程式的完成時間和 ui 實際 reflow 完成的時間不一致,故沒有精確的時間記錄,但測試結果效能排名依序 innerHTML > createDocumentFragment > 直接加入,直接修改 innerHTML 最佳。

修改CSS

由於修改文件中的 Element 的 CSS 屬性會造成 reflow 和 repaint,當要修改多項屬性時,應該使用 className 一次性修改

ChromeFirefoxSafariOperaIE9IE8IE7
直接修改122.8418.1254.4236.2638.6516.71510.2
cssText567.87904.6495.657522.4624.81695.1
setAttribute(“style”, …)466.449.7473.969.8538715.4no support
className2742.744.251.8226.2352.61489.4

執行次數 100000,10 次平均,時間單位 ms
結果:使用修改 className 效果最好,然而實務上屬性時常是變動值,無法預先建立 class,而不得已要修改 element 的 css 時,以 setAttribute 在大多數瀏覽器表現最好,但 IE7 不支援此方法。

承上,故有理論提出將 Element 隱藏或複製在程式中後處理,以避免 reflow

ChromeFirefoxSafariOperaIE9IE8IE7
直接修改101.5463253.8227632.85131509.5
display: none104.6457.3258.7230.1430.9517.11362
cloneNode97266252.1229.61658.61703.9891.5

執行次數 100000,10 次平均,時間單位 ms
結果:cloneNode 只在 Firefox 效能有提升,在 IE9 和 IE8 反而下降;而隱藏只在 IE9 有作用,總和來看,隱藏的方式效能是最平均。

變數名稱與註解

有些文章中提到,變數的名稱太長和註解會造成效能下降,然而實際上測試完全沒有影響,可以放心的使用。