這兩天在讀《高性能網站建設進階指南:Web開發者性能優化最佳實踐》
第七章講的是JavaScript的優化
關於JavaScript的代碼優化之前還沒看見過,感覺不錯,跟大家一起分享下~
1.作用域
執行JavaScript代碼時,JavaScript引擎會創建一個全局的執行上下文,而執行每一個函數時,也會創建一個對應的執行上下文,最終 建立一個執行上下文的堆棧。每一個執行上下文對應一個作用域鏈,用於解析標識符。JavaScript在作用域鏈中查找標識符。標識符在作用域鏈中的位置 越深,查找和訪問它所需的時間也就越長。(只有基於V8 JavaScript引擎的Google chrome和基於Nitro JavaScript引擎的Safari4+例外,它們的存取速度之快,使標識符深度的影響微乎其微了)標識符在作用域鏈的位置越靠上,存取速度就越快。
局部變量是目前為止JavaScript中讀寫速度最快的標識符。因為它們存在於執行函數的活動對象中,解析標識符只需查找作用域鏈中的單個對象。請儘可能的使用局部變量。任何非局部變量在函數中的使用超過一次時,都應該將其存儲為局部變量。
例如:
function createChildFor(elementId){ var element=document.getElementById(elementId), newElement=document.createElement('div'); element.appendChild(newElement); }
這個函數調用了兩次document全局變量,為了更快的引用,應該把它存儲到一個局部變量中。
全局變量對象始終是作用域鏈中最後一個對象,所以對全局標識符的解析總是最耗時的!
另外,存取數組中的某個值時,需要通過索引來查詢數據存儲的位置,而存取對象中的某個值時,需要通過屬性值來查詢數據存儲的的位置。所以,請將函數中使用超過一次的對象屬性或數組元素也存儲為局部變量。
再另外一下,在代碼執行過程中,作用域鏈通常是保持不變的,但是with語句和try-catch語句中的catch從句會臨時增長執行上下文的作用域鏈。
2.流控制
條件判斷優化:
在if語句中,隨著條件總數的增加,條件越深,性能損失也越大。
但是,並不是在條件均為離散值的情況下,switch語句的性能都由於if語句。在JavaScript中,當僅判斷一到兩個條件是,if語句通常比switch語句更快
優化條件判斷還有另一個辦法,數組查詢(假設要判斷的值value取值範圍為1-10):
var results=[result0,result1,result2,result3,result3,result4,result5,result6,result7,result8,result9,result10]; return results[value];
簡單的用數組索引映射value變量,雖然查詢數組的耗時也會隨著進入數組的深度而增加,但是和每個條件都用if語句或switch語句來判斷相比,增加的時間還是要小的很多。
循環優化:
簡單提升循環性能:
var values=[1,2,3,4,5]; for(var i=0;i<values.length;i++){//反覆的比較計數變量和數組長度,這樣做效率很低 values[i]++; } var length=values.length;//使用局部變量 for(var i=0;i<length;i++){//加快循環效率 values[i]++; } for(var i=length;i--;){ values[i]++; }//結束條件被改造為與0比較,一旦循環變量等於0,結束條件就會變為假,節約多達50%的執行時間!
for-in循環比其他循環慢,請在處理需要遍歷的對象屬性集或JSON對象的未知屬性集時才採用它
進一步優化循環–展開循環:
使一次循環完成多次循環的工作
var iterations=Math.ceil(values.length/8);//根據經驗,8是最佳數值,確定循環次數 var startAt=values.length%8;//要額外處理的數組項數量,即8的餘數,僅在第一次循環中使用,使用完後,重置為0 var i=0; do{ switch(startAt){ case 0:process(values[i++]); case 7:process(values[i++]); case 6:process(values[i++]); case 5:process(values[i++]); case 4:process(values[i++]); case 3:process(values[i++]); case 2:process(values[i++]); case 1:process(values[i++]); } startAt=0; }while(--iteration>0)
3.字符串優化
字符串連接:
字符串連接一直是JavaScript中性能最低的操作之一。
通常情況下的加法運算:
var text='hello'; text+=' '; text+='world';
這意味著要創建中間字符串來存儲連接的結果,頻繁地在後台創建和銷毀字符串會導致字符串連接的性能異常底下。
開發者利用了JavaScript的Array數組來補救:
var buffer=[],i=0; buffer[i++]='hello'; buffer[i++]=' ';//通過相應的索引值直接添加元素比調用push方法略快一點。 buffer[i++]='world'; var text=buffer.join('');
不過,如今瀏覽器對字符串的優化已經改變了字符串連接的局面。IE8+、ff、safari、chrome、opera已經對加法運算做了優化使其 比數組運算法更加快速。當字符串相對較小(少於20個字符)且連接的數量也較少時(少於1000個),所有瀏覽器使用加法運算符都能在不到1毫秒之內輕鬆 完成連接。但增加連接字符串的數量或大小時,在IE7中性能會明顯下降。在FF下,當字符串大小增加時,加法運算符和數組技術的性能差異會變小。在 Safari下,當字符串連接數量增加時,這兩種技術的性能差異也會同樣變小。只有在chrome和opera下,加法運算符一直保持著顯著的性能優勢。 所以,需要基於用戶的瀏覽器來權衡使用哪種技術。當然,大多數情況下,加法運算符是首選。
裁剪字符串:
JavaScript沒有用於移除字符串頭尾空白的原生修剪方法。
用於彌補的函數:
function trim(text){ return text.replace(/^\s+|\s+$/g,""); }
但是,這個函數可以在正則表達式上優化性能。
對性能的影響來自於正則表達式的兩個方面:一個方面是指明有兩個匹配模式的管道運算符,另一方面是指明全局應用該模式的g標記。保持正則表達式儘可能的簡單,可以提高性能。
Steven Levithan提出的在JavaScript中執行速度最快的裁剪字符串方式:
function trim(text){ text=text.replace(/^\s+/,"");//刪除頭部的空白 for(var i=text.length-1;i>=0;i--){//清除尾部的空白 if(/\S/.test(text.charAt(i))){ text=text.substring(0,i+1); break; } } return text; }
4.避免運行時間過長的腳本
JavaScript是單線程語言。所以在代碼執行時會導致頁面被凍結而出現假死。在一個時間段中,每個窗口或標籤頁都只能執行一個腳本,同時,所 有用戶的交互被中斷。如果JavaScript代碼未經過細心的設計,有可能長時間的凍結頁面,而導致瀏覽器停止響應。最常見的腳本執行時間過長的原因包 括:過多的DOM交互、過多的循環、過多的遞歸。
在複雜的WEB應用程序中,為了避免長時間的頁面凍結而導致不能進行交互,需要人為地插入中斷。
解決方案:使用定時器掛起。
window.onload=function(){ //頁面加載完成 //創建第一個定時器 setTimeout(function(){ //被延遲的腳本1 setTimeout(function(){ //被延遲的腳本2 },100); //被延遲的腳本1,繼續執行 },100); }
實際上就是把某些代碼排到JavaScript引擎隊列中稍後執行,而頁面可以利用這段引擎掛起的時間來進行交互。同樣也可將定時器應用於大型數組處理中
最後,推薦下這本書: