1. 程式人生 > 程式設計 >JS效能優化實現方法及優點進行

JS效能優化實現方法及優點進行

最近剛閱讀完《高效能javascript》,想談談對js效能優化的看法。理解有些不同,可能還需要各位多多提醒。

話不多說,提到javascript難免會聯想到文件物件模型(DOM),它作用於XML和HTML文件的程式介面(API),位於瀏覽器中,主要用來與HTML文件打交道。同樣也用於Web程式中獲取XML文件,並使用DOM API來訪問文件中的資料。儘管DOM是個與語言無關的API,它在瀏覽器中的介面卻是用javascript實現的。客戶端指令碼程式設計大多數時候是在和底層文件(underlying document) 打交道,DOM就成為現在javascript程式設計中的重要部分。

瀏覽器通常會把DOM和js獨立實現。比如在IE中,javascript的實現名為Jscript,位於jscript.dll檔案中;DOM的實現則存在另一個庫中,名為mshtml.dll(內部稱為Trident)。這個分離允許的其他技術和語言,比如VBScript,能共享使用DOM以及Trident提供的渲染函式。Safari中的DOM和渲染使用的Webkit中的WebCode實現,javascript部分是由獨立的javascriptCode引擎(最新版本的名字為SquirrelFish)來實現。Google Chrome同樣使用WebKit中的WebCore庫來渲染頁面,但javascript引擎是他們自己研發的,名為V8。Firefox的javascript引擎名為SpiderMonkey(最新版的名字為TraceMonkey),與名為Gecko的渲染引擎相互獨立。

把DOM和javascript(這裡指ECMAScript,JavaScript使用的ECMAScript版本為ECMAScript-262)各自想象一個島嶼,它們之間用收費橋樑連線。ECMAScript每次訪問DOM,都需要途經這座橋,並交納“過橋費”。訪問DOM的次數越多,費用越高。所以想辦法減少過橋次數就可以減少費用。

一、超載運輸

上面提到“過橋費”很貴啊,那麼我們儘量使需要多次去訪問DOM的時候全部整合到一次。比如最簡單的例子:

function innerHTMLLoop(){
   for(var count = 0;count < 15000 ;count++){
      document.getElementById('here').innerHTML +='a';
  } 
}

這個函式迴圈修改頁面元素的內容。這段程式碼存在迴圈迭代,該元素都被訪問兩次,一次是讀取innerHTML屬性值,另一次是重寫它。(意味著每次迴圈都必須付“過橋費”)。

為了減少費用,我們採取一種更高效的方法,例:

function innerHTMLLoop2(){
   var content = ' ';
   for(var count = 0;count < 15000 ;count++){
      content +='a';
  } 
  document.getElementById('here').innerHTML +=content;
}

這樣只需要付一次“過橋費”,就可以完成相同的功能。執行速度在不同的瀏覽器中都有大幅度的提升,例如IE6中,使用innerHTMLLoop2()比使用innerHTMLLoop()快155倍。(所以現實當中好多大卡車超載也是為了省這個費用,一次性多賺點。不過還是量力而行。程式也是一樣,均衡存乎萬物之間。)

二、觸手可及

儘管使用優化過的javascript引擎的新型瀏覽器,對於物件成員引用也存在一些效能問題。物件在原型鏈中存在的未知越深,找到它也就越慢,例如不太常見的寫法:window.location.href。每次遇到點操作符,巢狀成員會導致Javascipt引擎搜尋所有物件成員。物件成員巢狀得越深,讀取速度就會越慢。執行location.href總是比window.location.href要快,後者也比window.location.href.toString()要快。如果這些屬性不是物件的例項屬性,那麼成員解析還需要搜尋原型鏈,這會花更多的時間。

由於類似的效能問題都是與物件成員有關,因此應該儘可能避免使用它們。更準確地說,應當注意,只在必要時使用物件成員。例如,在同一個函式中沒有必要多次讀取同一個物件成員。例:

function hasEitherClass(element,className1,className2){
   return element.className == className1 || element.className == className2; 
}

以上的程式碼,element.className讀取了2次。意味著在該函式語句中2次成員查詢都是通過讀取屬性值,那麼有必要子啊第一次查詢到值後就將其儲存在區域性變數中,因為區域性變數的讀取速度要快很多。例:

function hasEitherClass(element,className2){
   var currentClassName = element.className;
   return currentClassName == className1 || currentClassName == className2; 
}

上面element.className 賦值在currentClassName區域性變數,避免了多次查詢帶來的效能開銷。(多次需要全域性物件成員,那就賦值在最容易拿到的地方,這樣可以減少去搜索和查詢)

總結

雖然我還有很多要講,但是太多太多的方式可以進行效能優化,以後有更好的再補充。不過優化就是跟人找方法用最小的力量去做最大的事情一樣,說俗點就是“懶”,我們讓程式也懶。

下面是一些關於客戶端JS效能的一些優化的小技巧:

1.[頂]關於JS的迴圈,迴圈是一種常用的流程控制。JS提供了三種迴圈:for(;;)、while()、for(in)。在這三種迴圈中 for(in)的效率最差,因為它需要查詢Hash鍵,因此應儘量少用for(in)迴圈,for(;;)、while()迴圈的效能基本持平。當然,推 薦使用for迴圈,如果迴圈變數遞增或遞減,不要單獨對迴圈變數賦值,而應該使用巢狀的++或--運算子。

2.如果需要遍歷陣列,應該先快取陣列長度,將陣列長度放入區域性變數中,避免多次查詢陣列長度。

3.區域性變數的訪問速度要比全域性變數的訪問速度更快,因為全域性變數其實是window物件的成員,而區域性變數是放在函式的棧裡的。

4.儘量少使用eval,每次使用eval需要消耗大量時間,這時候使用JS所支援的閉包可以實現函式模板。

5.儘量避免物件的巢狀查詢,對於obj1.obj2.obj3.obj4這個語句,需要進行至少3次查詢操作,先檢查obj1中是否包含 obj2,再檢查obj2中是否包含obj3,然後檢查obj3中是否包含obj4...這不是一個好策略。應該儘量利用區域性變數,將obj4以區域性變數 儲存,從而避免巢狀查詢。

6.使運算子時,儘量使用+=,-=、*=、\=等運算子號,而不是直接進行賦值運算。

7.[頂]當需要將數字轉換成字元時,採用如下方式:"" + 1。從效能上來看,將數字轉換成字元時,有如下公式:("" +) > String() > .toString() > new String()。String()屬於內部函式,所以速度很快。而.toString()要查詢原型中的函式,所以速度遜色一些,new String()需要重新建立一個字串物件,速度最慢。

8.[頂]當需要將浮點數轉換成整型時,應該使用Math.floor()或者Math.round()。而不是使用parseInt(),該方法用於將字串轉換成數字。而且Math是內部物件,所以Math.floor()其實並沒有多少查詢方法和呼叫時間,速度是最快的。

9.儘量作用JSON格式來建立物件,而不是var obj=new Object()方法。因為前者是直接複製,而後者需要呼叫構造器,因而前者的效能更好。

10.當需要使用陣列時,也儘量使用JSON格式的語法,即直接使用如下語法定義陣列:[parrm,param,param...],而不是採用 new Array(parrm,param...)這種語法。因為使用JSON格式的語法是引擎直接解釋的。而後者則需要呼叫Array的構造器。

11.[頂]對字串進行迴圈操作,例如替換、查詢,就使用正則表示式。因為JS的迴圈速度比較慢,而正則表示式的操作是用C寫成的API,效能比較好。

最後有一個基本原則,對於大的JS物件,因為建立時時間和空間的開銷都比較大,因此應該儘量考慮採用快取。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。