一、js概述
js即JavaScript,是被設計用來驗證表單、檢測瀏覽器、創建cookies、改進設計以及更多應用的網絡腳本語言,它非常容易使用。在web應用中,它是主要的編程語言,主要用途是進行各種邏輯控制、行為展現等。對於js的優化,對於整個應用的提升都是非常顯著的。
二、使用字面量(literal notation)來聲明對象和數組
創建對象和數組的方法有很多,但是使用字面量是最簡單最快的。傳統的方法是使用內建的構造器聲明:
//create an object var obj = new Object(); obj.debug = false; obj.lang = "en"; //create an array var arr = new Array("one", "two", "three");
這種方式在技術上是沒問題的,但是使用字面量聲明會更快而且代碼更少:
//create an object var obj = {debug: false, lang: "en"}; //create an array var arr = ["one", "two", "three"];
三、避免使用全局變量和函數
即把屬性和方法都綁定到一個命名空間對象里,這樣不僅可以減少命名沖突,而且可以提升程序性能。
當兩個區域的代碼使用同一個全局變量名作不同用途時,就會產生命名沖突。在JavaScript里,函數外定義的變量或對象都是全局的,隨着程序代碼和庫的增加,命名沖突的概率就越大。如果函數內或其他區域的代碼引用了一個特定的全局變量,腳本引擎就必須遍歷一遍作用域直到找到這個變量,局部變量則更容易找到。全局變量會在整個腳本的生命周期中存在,但是局部變量會及時被垃圾收集器回收。
例如以下使用全局的聲明(不高效):
//define global variables var lang = "en"; var debug = true; //define global function function 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 (+) operator str = "h" + "e"; //Using the shorthand assigment (+=) operator str += "l"; //Using string.concat() str = str.concat("l", "o"); //Using array.join() str = ["h", "e", "l", "l", "o"].join("");
如果你執行的連接操作次數較少,那么以上任何一種方式都可以。但是,當執行大量的連接操作時,就需要優化一下了:
//Slower: Concatenating strings with + operator str += "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 value eval("myValue = myObject." + myKey + ";"); //Correct usage: Using subscript notation to set a value myValue = 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的建議:
- 緩存計算過的樣式
- 對於固定的樣式,改變class的名詞而不是樣式;對於動態的樣式,改變cssText屬性:
// bad - changing the stle - accessing DOM multiple times var myElement = document.getElementById('mydiv'); myElement.style.borderLeft = '2px'; myElement.style.borderRight = '3px'; myElement.style.padding = '5px'; // good - use cssText and modify DOM once var myElement = document.getElementById('mydiv'); myElement.style.cssText = 'border-left: 2px; border-right: 3px; padding: 5px;';
- 當你要對一個DOM元素做出很多修改時,可以先進行一些‘預處理’,批量修改后再替換原始的元素
- 創建一個副本(cloneNode()),對這個副本進行更新,然后替代原來的節點
// slower - multiple reflows var 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 copy var 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的次數
// slower var subElem = document.createElement('div'), elem = document.getElementById('animated'); elem.appendChild(subElem); elem.style.width = '320px'; // faster var subElem = document.createElement('div'), elem = document.getElementById('animated'); elem.style.display = 'none'; // will not be repainted elem.appendChild(subElem); elem.style.width = '320px'; elem.style.display = 'block';
- 創建一個文檔片段(使用DocumentFragment()),修改完成后,再把它追加到原始文檔中
// slower var 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 var 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
- 創建一個副本(cloneNode()),對這個副本進行更新,然后替代原來的節點