轉自http://www.cnblogs.com/xiangzi888/archive/2013/01/19/2867630.html
一、js概述
js即JavaScript,是被設計用來驗證表單、檢測瀏覽器、建立cookies、改進設計以及更多應用的網路指令碼語言,它非常容易使用。在web應用中,它是主要的程式設計語言,主要用途是進行各種邏輯控制、行為展現等。對於js的最佳化,對於整個應用的提升都是非常顯著的。
二、使用字面量(literal notation)來聲明對象和數組
建立對象和數組的方法有很多,但是使用字面量是最簡單最快的。傳統的方法是使用內建的構造器聲明:
//create an objectvar obj = new Object();obj.debug = false;obj.lang = "en"; //create an arrayvar arr = new Array("one", "two", "three");
這種方式在技術上是沒問題的,但是使用字面量聲明會更快而且代碼更少:
//create an objectvar obj = {debug: false, lang: "en"}; //create an arrayvar arr = ["one", "two", "three"];
三、避免使用全域變數和函數
即把屬性和方法都綁定到一個命名空間對象裡,這樣不僅可以減少命名衝突,而且可以提升程式效能。
當兩個地區的代碼使用同一個全域變數名作不同用途時,就會產生命名衝突。在JavaScript裡,函數外定義的變數或對象都是全域的,隨著程式碼和庫的增加,命名衝突的機率就越大。如果函數內或其他地區的代碼引用了一個特定的全域變數,指令碼引擎就必須遍曆一遍範圍直到找到這個變數,局部變數則更容易找到。全域變數會在整個指令碼的生命週期中存在,但是局部變數會及時被垃圾收集器回收。
例如以下使用全域的聲明(不高效):
//define global variablesvar lang = "en";var debug = true; //define global functionfunction setLang (arg) { lang = arg;}
使用如下聲明則更好:
var myApp = { lang: "en", debug: true,}; myApp.setLang = function (arg) { this.lang = arg;}
四、高效的使用try catch語句
你可以使用try-catch語句來攔截程式拋出的錯誤(在瀏覽器處理之前),這對於向使用者隱藏錯誤或者為使用者定製錯誤資訊是很有用的。
當try結構中發生錯誤時,程式會立即停止並跳到catch結構(會提供錯誤對象)中。在catch結構中,錯誤對象會賦給一個新的變數,新的變數在catch結構中一直存在,直到catch語句結束。建立並處理這個新的運行時變數會影響到程式的效能,在關鍵功能和迴圈中應避免使用try-catch結構。例如:
var object = ['foo', 'bar'], i;for (i = 0; i < object.length; i++) { try { // do something } catch (e) { // handle exception }}
以上這段代碼可能會拋出多個錯誤,這樣寫可能會更好:
var object = ['foo', 'bar'], i;try { for (i = 0; i < object.length; i++) { // do something }} catch (e) { // handle exception}
五、使用賦值運算來連接字串
字串串連是很常用的操作,也有很多種方式,比如:
//Using the concatenation (+) operatorstr = "h" + "e"; //Using the shorthand assigment (+=) operatorstr += "l"; //Using string.concat()str = str.concat("l", "o"); //Using array.join()str = ["h", "e", "l", "l", "o"].join("");
如果你執行的串連操作次數較少,那麼以上任何一種方式都可以。但是,當執行大量的串連操作時,就需要最佳化一下了:
//Slower: Concatenating strings with + operatorstr += "x" + "y";
以上串連操作比較慢,它會按以下步驟執行(參見‘編譯原理’):
- 建立一個臨時變數
- 串連後的字串xy被賦給這個臨時變數
- 臨時變數與str的當前值相加
- 結果賦給str變數
你可以使用如下的方式避免使用臨時變數(減少記憶體的使用):
str += "x";str += "y";
六、最佳化你的迴圈
當你使用迴圈的時候,你可以通過減少每次迭代時工作量來最佳化迴圈的整體效能。例如:
for (var i = 0; i < arr.length; i++) { // length of arr is recalculated every time}
在以上代碼中,arr.length在每次迴圈中都被計算了一次,這是不必要的,可以聲明一個局部變數len來緩衝這個值,就會提高運行速度:
for (var i = 0, len = arr.length; i < len; i++) { // cache the length of the array}
或者為了進一步最佳化,考慮反向的執行迴圈(如果不關心數群組成員的順序的話):
for (var i = arr.length; i--;) { // in reverse}
七、避免使用eval()方法
eval()方法可以執行一段JavaScript代碼,應該避免使用的原因:
- 效能較差,它必須調用編譯器來傳遞其參數,然後執行
- 安全問題,因為它會執行傳遞給它的任何代碼,所以容易受各種注入攻擊,特別是在來源未知的時候
- 不利於調試,eval的參數是動態產生的,調試起來不方便,可讀性也較差
//Incorrect usage: Using eval to set a valueeval("myValue = myObject." + myKey + ";");//Correct usage: Using subscript notation to set a valuemyValue = myObject[myKey];
另外timeout函數中的setTimeout()和setInterval()也可以接受字串參數,然後執行,因此表現跟eval()一樣。應該避免傳遞字串,如下:
// Incorrect usage: Passing a string to setInterval()var oElement = null;setInterval('oElement = document.getElementById("pepe");', 0);// Correct usage: Passing a function to setInterval()var oElement = null;setInterval(function() { oElement = document.getElementById("pepe");}, 0);
八、使用事件委託
在處理DOM事件的時候,你可以僅對一個父元素綁定一個事件而不是每一個子項目。這種技術即事件委託,它利用事件冒泡來分配事件處理常式,可以提高指令碼的效能。比如,一個div元素下面有10個按鈕,你可以給div綁定一個監聽事件,而不是給10個按鈕分別綁定一個事件。傳統的聲明方式:
<a href="javascript:handleClick();">Click</a><button id="btn1" onclick="handleClick();">One</button><button id="btn2" onclick="handleClick();">Two</button>
為了提高代碼的效能,我們可以加一個div父元素,事件會向上冒泡,直到被處理。事件對象是觸發事件的元素,我們可以根據它的id屬性來判斷是哪一個元素觸發了事件:
<div id="btngroup"> <button id="btn1">One</button> <button id="btn2">Two</button></div>document.getElementById("btngroup").addEventListener("click", function (event) { switch (event.srcElement.id) { //firefox 下為 event.target.id case "btn1": handleClick(); break; default: handleClick(); }}, false); // type, listener, useCapture (true=beginning, false=end)
九、盡量減少DOM操作
DOM是一個包含了很多資訊的複雜的API,因此即使是很小的操作可能會花費較長的時間執行(如果要重繪頁面的話)。為了提高程式效能,應盡量減少DOM操作,這裡有一些建議:
1.減少DOM的數目
DOM節點的數目會影響與它相關的所有操作,要盡量使DOM樹小一些:
- 避免多餘的標記和嵌套的表格
- 元素數盡量控制在500個以內(document.getElementsByTagName('*').length)
2.緩衝已經訪問過的節點
當訪問過一個DOM元素後,就應該把它緩衝起來,因為你的程式往往要重複訪問某個對象的,例如:
for (var i = 0; i < document.images.length; i++) { document.images[i].src = "blank.gif";}
以上例子中,docum.images對象被訪問了多次,這並不高效,因為每一次迴圈中,瀏覽器都要尋找這個元素兩次:第一次讀取它的長度,第二次改變相應的src值。更好的做法是先把這個對象儲存起來:
var imgs = document.images;for (var i = 0; i < imgs.length; i++) { //當然也可以把 imgs.length 提前算出來,這裡不是重點 imgs[i].src = "blank.gif";}
十、減少頁面重繪
在控制DOM元素數目的同時,你還可以通過減少修改元素(減少頁面的重繪)的方法來提高效能。重繪有兩種方式:repaint、reflow。
1.repaint,也叫redraw,即改變了元素的視覺效果,但是不影響它的排版(比如改變背景顏色)
2.reflow,會影響部分或者全部頁面的排版,瀏覽器不僅要計算該元素的位置,還要計算它影響到的周圍的元素位置
當你要改變頁面配置的時候,reflow就發生了,主要有如下情況:
- 增加或刪除DOM節點
- 改變元素的位置
- 改變元素的尺寸(如margin,padding,border,font,width,height等)
- 調整瀏覽器視窗的尺寸
- 增加或刪除css
- 改變內容(如使用者輸入表單)
- 命中css選取器(如hover)
- 更改了class屬性
- 利用指令碼更改了DOM
- 檢索一個必須被計算的尺寸(如offsetWidth,offsetHeight)
- 設定了一個css屬性
這裡有一些減少頁面重繪的建議:
css的建議:
- 改變class屬性時應盡量少的影響到周圍的元素節點
- 避免聲明多個內聯的樣式(把多個樣式放在一個外部檔案裡)
- 有動畫的元素使用絕對位置,這樣不會影響其他元素
- 避免使用table來排版,如果需要使用儲存資料,那麼要固定排版(table-layout:fixed)
js的建議:
- 當你要對一個DOM元素做出很多修改時,可以先進行一些‘預先處理’,批量修改後再替換原始的元素
- 建立一個副本(cloneNode()),對這個副本進行更新,然後替代原來的節點
// slower - multiple reflowsvar list = ['foo', 'bar', 'baz'], elem, contents;for (var i = 0; i < list.length; i++) { elem = document.createElement('div'); content = document.createTextNode(list[i]); elem.appendChild(content); document.body.appendChild(elem); // multiple reflows} // faster - create a copyvar orig = document.getElementById('container'), clone = orig.cloneNode(true), // create a copy list = ['foo', 'bar', 'baz'], elem, contents;clone.setAttribute('width', '50%');
- 修改一個不可見的元素,可以先讓其不可見(display:none),修改完成後,再恢複其可見(display:block),這樣就會減少reflow的次數
// slowervar subElem = document.createElement('div'), elem = document.getElementById('animated');elem.appendChild(subElem);elem.style.width = '320px'; // fastervar subElem = document.createElement('div'), elem = document.getElementById('animated');elem.style.display = 'none'; // will not be repaintedelem.appendChild(subElem);elem.style.width = '320px';elem.style.display = 'block';
- 建立一個文檔片段(使用DocumentFragment()),修改完成後,再把它追加到原始文檔中
// slowervar list = ['foo', 'bar', 'baz'], elem, contents;for (var i = 0; i < list.length; i++) { elem = document.createElement('div'); content = document.createTextNode(list[i]); elem.appendChild(content); document.body.appendChild(elem); // multiple reflows} // fastervar fragment = document.createDocumentFragment(), list = ['foo', 'bar', 'baz'], elem, contents;for (var i = 0; i < list.length; i++) { elem = document.createElement('div'); content = document.createTextNode(list[i]); fragment.appendChild(content);}document.body.appendChild(fragment); // one reflow