《JavaScript 代碼優化指南》


  ~~教你向老鳥一樣敲代碼~~。

 


 

1. 將腳本放在頁面的底部  

...
        <script src="./jquery.min.js"></script>
        <script src="./index.js"></script>
    </body>
</html>

 

2. 變量聲明合並 

  將多條var語句合並為一條語句,我建議將未賦值的變量放在最后面。
  並且為了代碼的美觀,還可以將等號對齊。

//糟糕
var oBtn = document.getElementById('button');
var name = '';
var index;
var oLis = document.getElementsByTagName('li');
var result = [1,2,3,4];
var i;
    

//建議
var oLis   = document.getElementsByTagName('li'),
    oBtn   = document.getElementById('button'),
    result = [1,2,3,4],
    name   = '',
    index,
    j;

  

3. 減少全局變量 

  減少全局變量,並不是說不定義全局變量,而是我們可以定義一個對象,來保存我們定義的全局變量。
  我稱這個對象為變量空間。

//不推薦
var global = 'This is global Variant',
   config = {},
   data = [];

// 建議:

var varSpace = {
   global:'This is global Variant',
   config:{},
   data:[]
 };

 

4. 字面量方式

  聲明枚舉類型的變量,應該采用字面量方式。

//糟糕的
  var object = new Object(),
      array = new Array(),
      pattrn = new RegExp();
//建議:
  var object = {},
      array = [],
      pattrn = //;

 

5. 緩存DOM

  DOM 遍歷是非常昂貴的,所以要盡量將會重用的DOM保存起來。

// 糟糕
var height = document.getElementById('box').offsetHeight;
document.getElementById('box').style.background="#f00";

// 建議
var oBox = document.getElementById('box'),
    height = oBox.offsetHeight;
oBox.style.background="#f00";

 

6. 使用緩存的父元素來查詢子元素

  正如前面我們所說的DOM的操作與獲取是非常昂貴的,所以對於獲取一個DOM元素,首先我們應該檢查它有沒有已經被緩存了的父元素。如果存在的話,可以直接在父元素的基礎上向內去查找。

// 糟糕的
    var oBox = document.getElementById('box'),
        oLis = document.getElementsByTagName('li');

// 建議
    var oBox = document.getElementById('box'),
        oLis = oBox.getElementsByTagName('li');

 

7. 條件分支優化

  7.1 選擇接近 

  將條件分支,按可能性的順序從高到低排列,可以減少JavaScript 解釋器對條件語句的探測次數。
  例如預計一個數在0~99的范圍概率是50%,預計100 - 199的范圍時 15% 在 200 - 300 的范圍時 30%,最后小於0的概率的是5%。
  使用分支語句則可以這樣組織代碼:

if(num>=0 && num<100){
    //...
}else if(num >=200 && num <= 300){
    //...
}else if(num >=100 && num < 200){
    //...
}else{
    // 小於0
}

     實際上,還可以進一步優化:

if(num >=0){
    if(num < 100){
        //...
    }else if(num >= 200 && num <=300){
        //...
    }else if(num >=100 && num < 200){
        //...
    }
}else{
    // 小於0
}

  7.2 縮短檢測條件

    對檢測的條件進行優化。

· 字符串為空或非空
//不推薦:
    if (name === '') {
        // ......
    }
    if (name !== '') {
        // ......
    }
//推薦:
    if (!name) {
        // ......
    }
    if (name) {
        // ......
    }

· 數組非空

//不推薦:
    if (collection.length > 0) {
        // ......
    }
//推薦:
    if (collection.length) {
        // ......
    }

· 布爾不成立

//不推薦:
    if (notTrue === false) {
        // ......
    }
//推薦:
    if (!notTrue) {
        // ......
    }

· null 或 undefined

//不推薦:
    if (noValue === null || typeof noValue === 'undefined') {
      // ......
    }
//推薦:
    if (noValue == null) {
      // ......
    }

  7.3 三目運算符

  使用三目運算符可以代替簡單的雙分支結構。
  例如:

if(a > b){
    num = a
}else{
    num = b
}

    用三目運算符:  num = a > b ? a : b; 

    這里我推薦簡單的條件判斷,也就是在條件的語句組中,語句並不多,例如下面這種情況,我就不建議使用三目運算符。

if(a > b){
    num = a;
    array.push(num);
    string = array.join('');
    ...
}

  7.4  當分支數量大於等於3時,采用switch語句 

    測試表明,當分支數量大於3時,switch 的分支執行效率要高與if分支。在IE中尤為明顯,其性能可以提升50%左右。

// 糟糕 ...
if(num == a){

}else if(num == b){

}else if(num == c){

}else{
    
}

// 推薦:
switch(num){
        case a:
            ...
            break;
        case b:
            ...
            break;
        case c:
            ...
            break;
        default:
            ....
 }

 

8. 循環優化

  8.1 不在循環中聲明變量。

    在循環中去聲明變量或者是定義函數,會嚴重的消耗性能.

//糟糕:
    for(var i=0,l=data.length;i<l;i++){
        if(data[i] > 10){
            var value = data[i]
        }
    }

//建議:
    var value ;
    for(var i=0,i<data.length;i++){
        if(data[i] > 10){
            value = data[i]
        }
    }

  8.2 優化循環終止條件 

    由於每次循環過程都會去計算終止條件,所以可以用一個變量來保存這個終止條件,減少重復性的計算,提高執行速度。

//糟糕的:
    for(var i=0;i<data.length;i++){//...}

//建議:
    for(var i=0,l=data.length;i<l;i++){//...}

  8.3 優化循環體 

    循環體是執行最多的,所以要確保其最大程度的優化。

  8.4 for in 循環的優勢 

    for in 循環相比於普通的循環,它更加適合用於遍歷對象,這是因為for in 循環不僅可以返回對象的 value值,還可以返回對象當前的key。
    示例:

for( key in Object){
    if(Object.hasOwnProperty(key)){
        //...
    }
}
// 注: 參考《JavaScript 語言精粹》

 

9. 巧用 || 和 && 布爾運算符

  · 短路求值

//糟糕的:
    if(!fn){
        callback = function(){}
    }else{
        callback = fn;
    }
//建議:
    callback = fn || function(){};

  · 短路檢測執行

//糟糕的:
    if(name){
        welcome();
    }
//建議:
    name && welcome();

 

10. 符串優化

   · 定界符 

    統一使用單引號(‘),不使用雙引號(“)。這在創建 HTML 字符串非常有好處:
    示例:

 var msg = 'This is some HTML <div class="makes-sense"></div>';

   · 字符串拼接

     對於大量的字符串拼接的情況下,不建議使用 += 進行拼接,而是建議先定義一個數組,然后不斷的push,最后再使用join('')進行字符串的拼接

   · 換一種角度來看待字符拼接

     當你遍歷一個數組或對象時,不要總想着使用for語句,要有創造性,總能找到更好的辦法,例如向下面這樣。

var arry = ['item1','item2','item3','item4'],
    string = '<ul><li>'+ arry.join('</li><li>')+'</li></ul>';

  · String的隱式轉換

    當有調用String對象的方法或屬性時,JavaScript會進行一個隱式裝箱操作,將字符串先轉換成一個String對象。再去調用對應的方法。
    例如:
       'hellow'.length  實際上進行了  new String('hellow')  ->  new String('hellow').length 
    所以,對於會用到String對象的方法或屬性的字符串,我推薦通過 new String() 的方式來聲明定義。

 

11. 數組相關

  · 獲取數組最后一個元素

var array = [1,2,3],
lastValue = array[array.length-1];
lastValue = array.slice(-1);

  · 數組截斷

var array = [1,2,3];
array.length = 0;

   · 數組塞值   

    對於按順序增加數組元素,可以在循環中通過push依次添加。
    示例:

var array = [];
for(var i=0,l=data.length;i<l;i++){
    // 糟糕
    array[i] = i;
    // 推薦
    array.push(i);
}

 

12. 函數相關

  12.1 作用域提升

    在 JavaScript 中變量和方法定義會自動提升到執行之前。JavaScript 只有 function 級的定義域,而無其他很多編程語言中的塊定義域,所以使得你在某一 function 內的某語句和循環體中定義了一個變量,此變量可作用於整個 function 內,而不僅僅是在此語句或循環體中,因為它們的聲明被 JavaScript 自動提升了。我們通過例子來看清楚這到底是怎么一回事:
    原 function

myname = "global"; 
function sample() {
   alert(myname);   // "undefined"
   var myname = "local";
   alert(myname);   // "local"
}
sample()

    被 JS 提升過后

myname = "global"; 
function sample() {
   var myname;        //沒有賦值
   alert(myname);    // "undefined"
   myname = "local";//此處賦值
   alert(myname); // "local"
}
sample();

    * 需要注意的是,雖然作用域內的變量被自動提升到最前進行聲明,但是變量賦值的依然是代碼中的位置。
      正如你所看到的這段令人充滿困惑與誤解的代碼導致了出人意料的結果。只有良好的聲明習慣,也就是下一章節我們要提到的聲明規則,才能盡可能的避免這類錯誤風險。

      聲明提前
      為避免上述的變量和方法定義被自動提升造成誤解,把風險降到最低,我們應該手動地顯示地去聲明變量與方法。也就是說,所有的變量以及方法,應當定義在 function 內的首行。

// 不推薦
function sample() {
   var first = 0;
   var last = 1;
   if(last > first){
        var sum = first + last;
   }
   var count = sum;
}

// 推薦
function sample() {
   var first = 0,
       last = 1,
       sum = 0,
       count = 0;

   if(last > first){
        sum = first + last;
   }
    count = sum;
}

  12.2 函數的聲明    

    JS會自動將函數的聲明上調到函數的執行之前。
    切勿在語句塊內聲明函數,在 ECMAScript 5 的嚴格模式下,這是不合法的。函數聲明應該在定義域的頂層。但在語句塊內可將函數申明轉化為函數表達式賦值給變量。

//不推薦:
    if (x) {
      function foo() {}
    }
//推薦:
    if (x) {
      var foo = function() {};
    }

  12.3 參數設計  

    函數的參數,建議控制在6個以內,應為過多的參數便會導致維護成本的提升。
    對於需要多個參數的函數,建議將所有參數封裝成一個對象options進行傳輸。

//不推薦:
    function getData(params1,params2,params3,params4...){//...}
//建議:
    function getData(options){//...}
    getData({'prams1':'','params2':,....});

  12.4 閉包與立執行函數

     · 全局命名空間污染與 IIFE   

      總是將代碼包裹成一個 IIFE(Immediately-Invoked Function Expression),用以創建獨立隔絕的定義域。這一舉措可防止全局命名空間被污染。
      IIFE 還可確保你的代碼不會輕易被其它全局命名空間里的代碼所修改(i.e. 第三方庫,window 引用,被覆蓋的未定義的關鍵字等等)。

//不推薦
    var x = 10,
        y = 100;
    console.log(window.x + ' ' + window.y);
//推薦:
    (function(w){
      'use strict';
      var x = 10,
          y = 100;
        w.console.log((w.x === undefined) + ' ' + (w.y === undefined));
    }(window));

    · IIFE(立即執行的函數表達式)

      無論何時,想要創建一個新的封閉的定義域,那就用 IIFE。它不僅避免了干擾,也使得內存在執行完后立即釋放。
      所有腳本文件建議都從 IIFE 開始。
      立即執行的函數表達式的執行括號應該寫在外包括號內。雖然寫在內還是寫在外都是有效的,但寫在內使得整個表達式看起來更像一個整體,因此推薦這么做。

//不推薦
    (function(){})();
//推薦:
    (function(){}());

      用下列寫法來格式化你的 IIFE 代碼:

(function($, w, d){
    'use strict';
    $(function() {
        w.alert(d.querySelectorAll('div').length);
    });
}(jQuery, window, document));

    · 嚴格模式      

      ECMAScript 5 嚴格模式可在整個腳本或獨個方法內被激活。它對應不同的 javascript 語境會做更加嚴格的錯誤檢查。嚴格模式也確保了 javascript 代碼更加的健壯,運行的也更加快速。
      嚴格模式會阻止使用在未來很可能被引入的預留關鍵字。
      你應該在你的腳本中啟用嚴格模式,最好是在獨立的 IIFE 中應用它。避免在你的腳本第一行使用它而導致你的所有腳本都啟動了嚴格模式,這有可能會引發一些第三方類庫的問題。

//不推薦:
    'use strict';
    (function(){
        // some code
    }());
//推薦:
    (function(){
        'use strict';
        // some code
    }());

 

13. 數據類型

  13.1 類型檢測 

    javaScript中數據類型,可以分為 值類型、枚舉類型、null、undefined。
    值類型可以通過typeof進行判斷
    枚舉類型則可以通過 instanceof
    而null、undefined則可以直接通過null進行判斷。
    示例:
      值類型通過typeof直接進行檢測

// string
typeof variable -->'string'

// number
typeof variable --> 'number'

// boolean
typeof variable --> 'boolean'

// Function
typeof variable --> 'function'

      枚舉類型,通過以下方法是無法進行判斷的

//null
typeof variable --> 'object'
// object
typeof variable --> 'object'
// Date
typeof variable --> 'object'

      對於枚舉類型就要采用instanceof進行判斷

// RegExp
variable instanceof RegExp --> true

// Array
variable instanceof Array  --> true

      而 undefined 或 null 則統一采用 undefined進行判斷。

// null or undefined
 variable == null  --> true

    更具體的關於JavaScript數據類型判斷方法,可以參見我的另一篇博客:[淺玩JavaScript的數據類型判斷]

  13.2 類型判斷  

    總是使用 === 精確的比較操作符,避免在判斷的過程中,由 JavaScript 的強制類型轉換所造成的困擾。
    如果你使用 === 操作符,那比較的雙方必須是同一類型為前提的條件下才會有效。
    在只使用 == 的情況下,JavaScript 所帶來的強制類型轉換使得判斷結果跟蹤變得復雜,下面的例子可以看出這樣的結果有多怪了:

(function(){
  'use strict';
  
  console.log('0' == 0); // true
  console.log('' == false); // true
  console.log('1' == true); // true
  console.log(null == undefined); // true
  
  var x = {
    valueOf: function() {
      return 'X';
    }
  };
  
  console.log(x == 'X');
  
}());

   13.3 類型轉換

    · 字符轉數值    

      PS:這里針對的是字符串類型的數值。

//不推薦:
     Number(str);
//推薦:
    variable*1;
    varIable-1;
    +variable;
//當字符串結尾包含非數字並期望忽略時,使用parseInt
    parseInt(variable)
//示例:
    var width = '200px';
    parseInt(width, 10);

    · 小數轉整數

//不推薦:
      parseInt(variable)
//推薦: 
      ~~variable
//備選:
    Math.ceil();
    Math.floor();

    · 轉換為布爾型  

//不推薦:
    Boolean(variable);
//推薦: 
    !variable
    !!variable

    · 轉換為字符串

//不推薦:
    String(variable);
    variable.toString();
//推薦: 
    num + '';

 

14. DOM插入的優化

   · 不在循環中插入DOM

    更改頁面的DOM結構基本上都會造成瀏覽器對頁面的重新渲染,這所造成的代價是非常龐大的,如果還在循環中去操作DOM,這就更加不能容忍。

//糟糕的:
 for(var i=0,l=arr.length;i<l;i++){
     var oDiv = document.createElement('div');
     document.body.appendChild(oDiv);
 }
//建議: 
 var doc = document.createDocumentFragment();
 for(var i=0,l=arr.length;i<l;i++){
     var oDiv = document.createElement('div');
      doc.appendChild(oDiv);
 }
 document.appendChild(doc);

  · innerHTML

    利用文檔碎片節點適合更改基本的DOM塊,但是對於大范圍的DOM更改,使用innerHTML不僅更加簡便,而且相比於標准的DOM操作方法,更加的高效。

   var htmlStr = '';
   for(var i=0;i<50;i++){
        htmlStr += '<div>'+i+'</div>';
   }
   document.body.innerHTML = htmlStr;

 

15. 事件代理

  事件代理是利用了事件的冒泡特性。我們可以為一個統一的父節點綁定事件,然后去進行目標子節點的事件處理。
  事件代理可以減少相同事件類型的綁定次數,另外可以解決在JS生成DOM時,必須在生成之后才能綁定事件的問題。最后事件代理還可以解決事件處理程序與所觸發事件的DOM對象之間的循環引用問題。
  事件代理的實現很簡單: 

    HTML 結構:

 <div id="box"><div id="son"></div>

    JavaScript:

document.getElementById('box').onclick=function(e){
    var ev = e || window.event,
        oSrc = ev.target || ev.srcElement;

    if(oSrc.id.indexOf('son')){
        alert('此時進行#son元素的事件處理');
    }

}

 

16. 注意NodeList  

  NodeList 是一組節點列表,它在形態上很接近數組,但並不是數組,通常我們稱之為偽數組。
  NodeList 的獲取主要是通過以下的方法與屬性。
    · document.getElementsByTagName('')
    · 訪問了HTML-DOM的快捷集合屬性

      · document.images
      · document.forms
      · document.links

    · 獲取了DOM的 childNodes 屬性
    · 獲取了DOM的 attributes 屬性
  了解了NodeList,我們還需要知道的是該怎么最小化去訪問NodeList次數以便於改進腳本的執行性能:
  這里可以參考循環的優化方式,比如循環的終止條件:

 for(var i=0,l=document.forms.length;i<l;i++){
            //....
  }

 

17. 檢查對象是否具有指定的屬性與方法

  當你需要檢測一些屬性是否存在,避免運行未定義的函數或屬性時,這個小技巧就顯得很有用。
  如果打算定制一些跨兼容的瀏覽器代碼,你也可能會用到這個小技巧。
  例如,你想使用document.querySelector() 來選擇一個id,並且讓它能兼容IE6瀏覽器,但是在IE6瀏覽器中這個函數是不存在的,那么使用這個操作符來檢測這個函數是否存在就顯得非常有用,如下面的示例:

    if('querySelector' in document){
        document.querySelector('#id');
    }else{
        document.getElementById("id");
    }

 

18. 松散耦合

  我們強烈推薦前端的三層分離,結構層(HTML)、表現層(CSS)、行為層(JavaScript),但有時在工作中,我們又不能避免在我們的腳本中還附加了一些HTML或CSS代碼。既然這個問題目前解決不了,那么我們就要想到以目前的方法,怎么進行最大的優化。

   · HTML && JavaScript

    當腳本中含有HTML代碼時,以下方法可以根據自己的使用場合進行擇優選擇: 

      · createElement : 適合數量最少的DOM創建
      · 文檔碎片          : 適合成塊的DOM更改。
      · innerHTML      : 適合批量的DOM插入。

   · CSS && JavaScript

     當腳本中含有CSS代碼時,以下方法可以根據自己的使用場合進行擇優選擇:  

     · style : 適合為DOM添加樣式不多的情況下。
       · cssText :適合為少量的DOM插入批量的樣式。
       · className :通過為DOM添加類名,而類名對應的具體樣式內容,則存在一個CSS文件中,這是我更加推薦的。
     · <style> : 如果你需要插入為很多的DOM插入批量的樣式內容,那么通過JavaScript 創建一個 <style>標簽,也不失是一個解決方案。

 

19. 異常處理

try{
        // 嘗試運行

}catch(msg){

    throw "Error name:" + msg.name; // throw會在控制台拋出異常信息,注意:throw會阻塞程序執行。建議使用console.log
    throw "Error message:" + msg.message  

    /*  Error.Name 的常見錯誤信息:

        1. EvalError:eval_r()的使用與定義不一致 
        2. RangeError:數值越界 
        3. ReferenceError:非法或不能識別的引用數值 
        4. SyntaxError:發生語法解析錯誤 
        5. TypeError:操作數類型錯誤 
        6. URIError:URI處理函數使用不當

    */

}finally{
    // finally 最終不論是運行成功還是沒有運行成功都會執行。
}

  

更多JavaScript精彩代碼,可以繼續閱讀[精彩 JavaScript 代碼片段]

 


--- 只要發現更好的代碼書寫技巧與規范,我會持續不斷的更新 ----

 


 

如果覺得本文對您有幫助或者您心情好~可以支付寶(左)或微信(右)支持一下,就當給作者贊助杯咖啡錢了 ~~:


免責聲明!

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



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