JavaScript代碼優化指南

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

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

2. 變量聲明合併

將多條var語句合併爲一條語句,我建議將未賦值的變量放在最後面。而且爲了代碼的美觀,還能夠將等號對齊。css

//糟糕
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. 減小全局變量 

減小全局變量,並非說不定義全局變量,而是咱們能夠定義一個對象,來保存咱們定義的全局變量。
我稱這個對象爲變量空間。html

//不推薦
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保存起來。java

// 糟糕
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元素,首先咱們應該檢查它有沒有已經被緩存了的父元素。若是存在的話,能夠直接在父元素的基礎上向內去查找。jquery

// 糟糕的
    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;

· 數組塞值 

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));

 

    你應該在你的腳本中啓用嚴格模式,最好是在獨立的 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

 

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 最終不管是運行成功仍是沒有運行成功都會執行。
}
相關文章
相關標籤/搜索