web移動開發最佳實踐之js篇


一、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";

以上連接操作比較,它會按以下步驟執行(參見‘編譯原理’):

  1. 創建一個臨時變量
  2. 連接后的字符串xy被賦給這個臨時變量
  3. 臨時變量與str的當前值相加
  4. 結果賦給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元素數目的同時,你還可以通過減少修改元素(減少頁面的重繪)的方法來提高性能。重繪有兩種方式:repaintreflow

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM