來源:網易郵箱
迴圈是大多數程式語言都具備的基本功能,JS也不例外,不同之處在於JS是解釋型語言,執行於瀏覽器環境中,客戶端的軟硬體條件會對JS執行效率產生很大的影響。然而客戶端環境對於開發者是未知、多樣的,並且難以改變,所以優化程式碼質量是提高程式碼效率的主要途徑。
JS程式碼中,迴圈是比較容易導致效能問題的因素。理解迴圈特性進而有針對性地進行優化也許會帶來不錯的效能提升。
for、while、do-while迴圈:
這三種迴圈本身的迴圈效率相差不多,所以只要根據適合的應用場景選擇即可。
以for迴圈為例:
1 2 3 4 5 |
var aValues = ["a", "b", "c", "d"]; for(var i = 0; i < aValues.length; i += 1){ fDoSomethingA(aValues[i]); fDoSomethingA(aValues[i]); } |
上面例子中每次迴圈都要比較i與陣列的長度,所以每次都要重新讀取陣列長度,由如果陣列長度在迴圈中是不變的,這樣做就沒有必要,我們可以使用區域性變數代替length的讀取。同理,例子中,aValues[i]由於被讀取兩次以上,我們也可以將它賦值給區域性變數:
1 2 3 4 5 6 |
var aValues = ["a", "b", "c", "d"], nLength = aValues.length; for(var i = 0, sValue; i < nLength; i += 1){ sValue = aValues[i]; fDoSomethingA(sValue); fDoSomethingB(sValue); } |
如果迴圈的業務邏輯對迴圈順序不敏感,可以嘗試倒序迴圈,即將計數器遞減到0。
1 2 3 4 5 6 |
var aValues = ["a", "b", "c", "d"], nLength = aValues.length; for(var i = nLength, sValue; i -= 1;){ sValue = aValues[i]; fDoSomethingA(sValue); fDoSomethingB(sValue); } |
使用這種方式計數器預設與0進行比較,連區域性變數比較都省略了,理論上也能提高效率。
for-in迴圈:
for-in迴圈更像在窮舉,他用來遍歷物件屬性,我們知道物件屬性的查詢會一直延續到原型鏈頂端,這將大大降低迴圈效率。for-in迴圈的寫法上沒有什麼優化空間,需要在使用時遵循一定原則:儘量只在遍歷資料型物件的時候才使用for-in迴圈。
如果遍歷物件的屬性是明確的,可以使用陣列迴圈替代。
例如遍歷一個聯絡人物件:
1 2 3 4 |
var aContact = ["N", "FN", "EMAIL;PREF", ...]; for(var i = aContact.length; i -= 1;){ fDoSomething(aContact[i]); } |
Duff策略
Duff策略的主要原理是通過展開迴圈減少次數來提高效率。例如
一個普通迴圈:
1 2 3 |
for(var i = aValues.length; i -= 1){ fDoSomething(aValues[i]); } |
如果aValues.length == N,寫成以下這種方式的效率將比循壞來的高:
1 2 3 4 5 6 7 |
fDoSomething(aValues[0]); fDoSomething(aValues[1]); fDoSomething(aValues[2]); fDoSomething(aValues[3]); ... ... fDoSomething(aValues[N-1]); |
但如果N很大,這種寫法就不現實,而Duff策略是一種適中的迴圈展開策略。
近日在網易郵箱通訊錄聯絡人的初始化迴圈中加入了Duff策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
var nLength = aContacts.length, // 總輪數 nRounds = Math.floor( nLength / 8), // 額外餘量 nLeft = nLength % 8, i = 0; // 先處理餘量 if(nLeft){ do{ fFormat(aContacts[i ++]); }while(-- nLeft) } // 每輪執行8次格式化 if(nRounds){ do{ fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); fFormat(aContacts[i ++]); }while(-- nRounds) } |
如上所示,每輪迴圈可以執行8個聯絡人資料的格式化操作,還有一輪迴圈用於處理餘下的聯絡人。由此可見,在聯絡人較多的情況下總的迴圈次數大大降低,可以降低迴圈的消耗。另外,8是Duff策略提出的最優值。
實際測試時發現在IE下可以帶來10-20%以上的效能提升,而非IE瀏覽器中幾乎看不到區別。
結束語:在測試過程中發現非IE瀏覽器下,優化後和優化前的效率差距並不是很大,甚至可以忽略,這說明這些瀏覽器的JS引擎對迴圈做了很好的優化,對開發者是非常友好的表現,無奈IE6、7、8下差距很明顯,但這符合我們預期。因此,趕快對JS程式碼中的對迴圈進行優化吧!