標籤:js 全域變數 前端開發 效能最佳化
js效能最佳化
javascript是一種解釋型語言,效能無法達到和C、C++等編譯語言的水平,但還是有一些方法來改進。
1、迴圈
JavaScript中的迴圈方式有for(;;)、while()、for(in)3種。其中for(in)的效率極差,因為for(in)執行過程中需要查詢散列鍵。for(;;)和while()比較,while迴圈的效率要優於for(;;)。
2、局部變數和全域變數
局部變數的訪問速度更快,因為全域變數其實是全域對象的成員,而局部變數是放在函數的堆棧當中的。
3、不使用eval
使用eval函數相當於在運行時再次調用解釋引擎對內容進行解釋運行
4、減少對象尋找
因為JavaScript的特性,對於類是運算式a.b.c.d.e,需要至少4次查詢操作。首先檢查a在檢查a中的b,如此往下。應盡量避免出現這樣的運算式、可以利用局部變數把要訪問的最終結果放入一個臨時的位置進行查詢。
這一點可以和迴圈結合起來,例如對一個數組可以先取他的長度 var len = a.length
##其實java 中對於列表的迴圈也是先取size給一個臨時變數
5、字串串連
如果是追加字串,最好使用s+=anotherStr操作,而不要使用s=s+""
但是如果要串連多個字串,應該少用+= 例如:
java 代碼
s+=a;
s+=b;
s+=c;
應該寫成 s+=a+b+c;
如果是收集字串,比如收集字串,最好使用一個緩衝實現。具體的實現思路就是使用Javascript數組來收集每個字串,最好使用join方法將這些字串串連起來,如下面代碼所示:
var buf = new Array();
for(var i=0;i<100;i++){
buf.push(i.toString());
}
var all = buf.join("");
6、類型轉換
類型轉換是JavaScript編程中容易出錯的地方,因為JavaScript是動態類型語言,即弱類型語言,不能指定變數的具體類型。
1、把數字轉換成字串,應用""+1,雖然比較彆扭一點、但效率是最高的
(""+)>String()>.toString()>newString()
String()屬於內建函式,所以速度很快,toString()要查詢原型中的函數,new String()用於返回一個精確的副本。
2、浮點數轉換成整型 parseInt()用於將字串轉換成數字,應該使用Math.floor()或者Math.round()來實現浮點型和整型之間的轉換。
3、對於自訂的對象,如果定義了toString()方法進行類型轉換的話,推薦顯示調用toString()
7、使用直接量
以往我們都使用new Array(parm,parm1..)等形式,對於直接量的解釋JavaScript支援使用[param,param1....]來直接表達一個數組。
前一種方式調用Array內部構造器,而後一種方式是解釋引擎直接解釋的,故執行速度要略微快一點。同理var foo = {}比 var foo = new Object()快,var reg=/..../比 var reg=new RegExp()執行的快些。
8、字串遍曆
優先使用Regex
9、進階對象
自訂進階對象和Date、RegExp等物件建構時會消耗大量的時間和資源
10、插入HTML
document.write效率較低,innerHTML效率較高
11、下標查詢
使用直接的下標尋找一個對象的屬性比通過.name方法要快很多
12、建立DOM節點
通常我們可能會使用字串直接寫HTML語句來建立節點,實際上這樣有如下缺點:
1、無法保證代碼的有效性;
2、字串操作效率低。
應該使用documeng.createElement()方法。如果存在現成的樣板節點,應該使用cloneNode()方法。
所以第一原則就是只需要為IE6(未打補丁的JScript 5.6或更早版本)做最佳化!
如果你的程式已經最佳化到在IE6下可以接受的效能,那基本上在其他瀏覽器上效能就完全沒有問題。
因此,注意我下面講的許多問題在其他引擎上可能完全不同,例如在迴圈中進行字串拼接,通常認為需要用Array.join的方式,但是由於SpiderMonkey等引擎對字串的“+”運算做了最佳化,結果使用Array.join的效率反而不如直接用“+”!但是如果考慮IE6,則其他瀏覽器上的這種效率的差別根本不值一提。
JS最佳化與其他語言的最佳化也仍然有相同之處。比如說,不要一上來就急吼吼的做最佳化,那樣毫無意義。最佳化的關鍵,仍然是要把精力放在最關鍵的地方,也就是瓶頸上。一般來說,瓶頸總是出現在大規模迴圈的地方。這倒不是說迴圈本身有效能問題,而是迴圈會迅速放大可能存在的效能問題。
所以第二原則就是以大規模迴圈體為最主要最佳化對象。
以下的最佳化原則,只在大規模迴圈中才有意義,在迴圈體之外做此類最佳化基本上是沒有意義的。
目前絕大多數JS引擎都是解釋執行的,而解釋執行的情況下,在所有操作中,函數調用的效率是較低的。此外,過深的prototype繼承鏈或者多級引用也會降低效率。JScript中,10級引用的開銷大體是一次空函數調用開銷的1/2。這兩者的開銷都遠遠大於簡單操作(如四則運算)。
所以第三原則就是盡量避免過多的引用層級和不必要的多次方法調用。
特別要注意的是,有些情況下看似是屬性訪問,實際上是方法調用。例如所有DOM的屬性,實際上都是方法。在遍曆一個NodeList的時候,迴圈條件對於nodes.length的訪問,看似屬性讀取,實際上是等價於函數調用的。而且IE DOM的實現上,childNodes.length每次是要通過內部遍曆重新計數的。(My god,但是這是真的!因為我測過,childNodes.length的訪問時間與childNodes.length的值成正比!)這非常耗費。所以預先把nodes.length儲存到js變數,當然可以提高遍曆的效能。
同樣是函數調用,使用者自訂函數的效率又遠遠低於語言內建函數,因為後者是對引擎本地方法的封裝,而引擎通常是c,c++,java寫的。進一步,同樣的功能,語言內建構造的開銷通常又比內建函數調用要效率高,因為前者在JS代碼的parse階段就可以確定和最佳化。
所以第四原則就是盡量使用語言本身的構造和內建函數。
這裡有一個例子是高效能的String.format方法。String.format傳統的實現方式是用String.replace(regex, func),在pattern包含n個預留位置(包括重複的)時,自訂函數func就被調用n次。而這個高效能實現中,每次format調用所作的只是一次Array.join然後一次String.replace(regex, string)的操作,兩者都是引擎內建方法,而不會有任何自訂函數調用。兩次內建方法調用和n次的自訂方法調用,這就是效能上的差別。
同樣是內建特性,效能上也還是有差別的。例如在JScript中對於arguments的訪問效能就很差,幾乎趕上一次函數調用了。因此如果一個可變參數的簡單函數成為效能瓶頸的時候,可以將其內部做一些改變,不要訪問arguments,而是通過對參數的顯式判斷來處理。
比如:
Java代碼
function sum() {
varr = 0;
for (var i = 0; i < arguments.length; i++) {
r += arguments[i];
}
return r;
}
function sum() {
var r = 0;
for (var i = 0; i < arguments.length; i++) {
r+= arguments[i];
}
return r;
}
這個sum通常調用的時候個數是較少的,我們希望改進它在參數較少時的效能。如果改成:
Java代碼
function sum() {
switch (arguments.length) {
case 1: return arguments[0];
case 2: return arguments[0] + arguments[1];
case 3: return arguments[0] + arguments[1] + arguments[2];
case 4: return arguments[0] + arguments[1] + arguments[2] +arguments[3];
default:
var r = 0;
for (var i = 0; i < arguments.length; i++) {
r += arguments[i];
}
return r;
}
}
function sum() {
switch (arguments.length) {
case 1: return arguments[0];
case 2: return arguments[0] + arguments[1];
case 3: return arguments[0] + arguments[1] + arguments[2];
case 4: return arguments[0] + arguments[1] + arguments[2] +arguments[3];
default:
var r = 0;
for (var i = 0; i < arguments.length; i++) {
r += arguments[i];
}
return r;
}
}
其實並不會有多少提高,但是如果改成:
Java代碼
function sum(a, b, c, d, e, f, g) {
var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d +e : a + b + c + d : a + b + c : a + b : a : 0;
if (g === undefined) return r;
for (var i = 6; i < arguments.length; i++) {
r += arguments[i];
}
return r;
}
function sum(a, b, c, d, e, f, g) {
var r = a ? b ? c ? d ? e ? f ? a + b + c + d + e + f : a + b + c + d +e : a + b + c + d : a + b + c : a + b : a : 0;
if(g === undefined) return r;
for (var i = 6; i < arguments.length; i++) {
r+= arguments[i];
}
return r;
}
就會提高很多(至少快1倍)。
最後是第五原則,也往往是真實應用中最重要的效能障礙,那就是盡量減少不必要的對象建立。
本身建立對象是有一定的代價的,但是這個代價其實並不大。最根本的問題是由於JScript愚蠢之極的記憶體回收調度演算法,導致隨著對象個數的增加,效能嚴重下降(據微軟的人自己說複雜度是O(n^2))。
比如我們常見的字串拼接問題,經過我的測實驗證,單純的多次建立字串對象其實根本不是效能差的原因。要命的是在對象建立期間的無謂的記憶體回收的開銷。而Array.join的方式,不會建立中間字串對象,因此就減少了那該死的記憶體回收的開銷。
因此,如果我們能把大規模對象建立轉化為單一語句,則其效能會得到極大的提高!例如通過構造代碼然後eval——實際上PIES項目中正在根據這個想法來做一個專門的大規模對象產生器……
好了上面就是偶總結的JS最佳化五大原則。
除了這些原則以外,還有一些特殊情況,如DOM的遍曆,以後有時間再做討論。