平時我們書寫迴圈大致是這個樣子的:
| 代碼如下 |
複製代碼 |
function(){
//一般迴圈的書寫方式
for(var i=0; i<values.length; i++){
...
}
}
|
最佳化變數聲明
上面這個寫法在一開始學習JS的時候是沒有錯的,甚至對於絕大多數的物件導向語言這麼寫都是正確的書寫方式,但JS不同與其他物件導向的語言,他沒有塊及範圍,有的僅僅是函數範圍,所以說,上面的寫法並不是很規範,在某些情況下還會發生Bug ,當你使用了一個變數,然後不久在函數中又重新聲明的話,就可能產生邏輯錯誤。對於JavaScript,只要你的變數是在同一個範圍中(同一函數),它都被當做是聲明的,即使是它在var聲明前使用的時候。合理的寫法即把變數聲明在函數的開始,而不是在迴圈內部才開始定義變數。
| 代碼如下 |
複製代碼 |
function func(){
//最佳化JS的變數定義,變數定義在開始位置,避免產生塊級範圍的誤區
var i;
for(i=0; i<values.length; i++){
...
}
}
|
最佳化迴圈中動態集合讀取
最佳化了JS 迴圈的變數後,我們繼續深入最佳化。日常開發最常打交道的就是DOM 了,常常會遇到迴圈NodeList 的情況(不知道什麼是NodeList 對象的話請去複習基礎知識)。總而言之一般類似var divs = document.getElementsByTagName(“div”) 的這個divs 所引用的就是NodeList 對象,其他類似的NodeList 近親有NameNodeMap 和 HTMLCollection, 這三個集合每當文檔結構發生變化時,它們都會得到更新(而且還是動態更新)。這樣就會導致倆個問題,第一是效能問題,每當你修改NodeList 在讀取它時,你讀取的不是先前的NodeList 而是修改後動態更新的NodeList。第二會產生無限迴圈的Bug。舉個例子,下列代碼會導致無限迴圈:
| 代碼如下 |
複製代碼 |
function func(){
//導致NodeList無限迴圈的迴圈書寫方式
var divs = document.getElementsByTagName("div");
for(var i=0; i<divs.length; i++){
var div =document.createElement("div");
document.body.appendChild(div);
alert("Infinite loop");
}
}
func()
|
上例所示的迴圈代碼會導致一個嚴重的問題,每次迴圈都要對divs.length 求值,意味著會運行取得所有div元素的查詢,之後建立一個新的div元素添加的文檔中,因此div.length 的值每次迴圈後都會遞增。既然i 和divs.length 每次都會同時遞增,結果他們的值永遠不會相等,從而導致了無限迴圈。
為了避免出現這樣的低效率甚至於隱含Bug的迴圈書寫方式,對策就是盡量減少訪問NodeList 的次數。因為每次訪問NodeList,都會運行一次基於文檔的查詢。所以可以考慮從NodeList 中取得的值緩衝起來。下面是最佳化後的寫法:
| 代碼如下 |
複製代碼 |
function func(){
//避免NodeList無限迴圈的迴圈書寫方式
var divs = document.getElementsByTagName("div");
for(var i=0, len = divs.length ; i<len; i++){
var div =document.createElement("div");
document.body.appendChild(div);
alert("Infinite loop will not happen");
}
}
func()
|
很簡單吧,只要把divs.length 緩衝起來放在一個變數len 裡面,那麼無論NodeList以後怎麼變,都避免讀取,避免無限迴圈發生,因為JavaScript的解釋性,所以a.b.c.d.e,需要進行至少4次查詢操作,先檢查a再檢查a中的b,再檢查b中的c,如此往下。所以如果這樣的運算式重複出現,只要可能,應該盡量少出現這樣的運算式,利用局部變數,把它放入一個臨時的地方進行查詢。如果每次查詢div.length,就要額外進行一個操作,而預先把var len=div.length,則就少了一次查詢,效能的最佳化大大體現。 開始你會覺得這樣書寫很不適應,但多寫幾遍,你很快就會習慣這樣“彆扭”書寫方式的。
最佳化繼續最佳化!
用i+=1 代替i++
這個是從《Javascript語言精粹》裡面看來的,大致意思是說用了i++ 會有潛在的安全問題,++這個運算子可以前置又可以後置使得自由度過大,如果出現結尾沒有分號會導致些許的錯誤例如:a++b++c,你很難弄明白前面那個代碼是什麼意思。根據《Javascript語言精粹》上面提到,這個++ 或者–– 運算子慫恿過於詭異的寫法而促使出現糟糕的代碼。除了錯誤的架構外,它們是導致病毒和其他安全威脅的第二大元兇。所以JsLint有個選項plusplus 是檢測是否禁止使用了這些運算子。還有個原因是i+=1 比起i++ 更加“原生”可以提升效能,但是個人認為可讀性降低,根據需要各自取捨吧。那麼我們的代碼又要變成這個樣子:
| 代碼如下 |
複製代碼 |
function func(){
//減值迭代最佳化迴圈
for(var i=values.length-1; i>=0; i--){
...
}
}
|
讓最佳化來的更猛烈些吧!
減值迭代最佳化迴圈
大多數迴圈使用一個從0開始、增加到某個特定值的迭代器。在很多情況下,從最大值開始,在迴圈中不斷減值的迭代器更加高效。如果值的處理順序無關緊要,那麼迴圈可以改為i 減值,最佳化如下:
| 代碼如下 |
複製代碼 |
function func(){
//迴圈最佳化大串聯
var i; //最佳化變數聲明
var divs = document.getElementsByTagName("div");
for(i=divs.length-1; i>=0; i-=1){ //最佳化迴圈中動態集合讀取、減值迭代、用i-=1 代替 i--
...
}
}
|
迴圈最佳化大串聯
如果我們把上面所有的迴圈最佳化方式集中在一塊,寫法會是這樣子滴:
| 代碼如下 |
複製代碼 |
12345678 function func(){ //迴圈最佳化大串聯 var i; //最佳化變數聲明 var divs = document.getElementsByTagName("div"); for(i=divs.length-1; i>=0; i-=1){ //最佳化迴圈中動態集合讀取、減值迭代、用i-=1 代替 i-- ... } }
|
老實說,上面這個迴圈最佳化大串聯效能是提高了,但可讀性也降低到了一個新的高度。你代碼寫的再風騷,也得讓人有個接受的地步那,做為團隊開發,上面這個串聯似乎還是不是特別可取,個人還是提倡部分最佳化,以最佳化效能的前提下兼顧可讀性:
| 代碼如下 |
複製代碼 |
|
function func(){
//合理的迴圈最佳化
var i,len; //最佳化變數聲明
var divs = document.getElementsByTagName("div");
for(i=0, len = divs.length; i<len; i++){ //最佳化迴圈中動態集合讀取,必要時也可以用i+=1 代替 i++
...
}
} |
個人比較傾向於上面的這段JS迴圈。合理最佳化迴圈提升了代碼的效能,又兼顧了可讀性,可謂是一箭雙鵰,萬事大吉