優化 JavaScript 代碼

原文:http://code.google.com/intl/zh-CN/speed/articles/optimizing-javascript.html
譯文:http://www.yeeyan.com/articles/view/92135/47626


使用字符串
    字符串連接操作會對 IE6 和 IE7 的垃圾收集帶來很大的影響( IE8 和其他非 IE 瀏覽器中效果好一些)。如果你的用戶中有很大一部分在使用 IE6 或 IE7,就需要非常注意構建字符串的方式了。
示例代碼:

var veryLongMessage = 'This is a long string that due to our strict line length limit of '
                    + maxCharsPerLine
                    + ' characters per line must be wrapped. '
                    + percentWhoDislike
                    + '% of engineers dislike this rule. The line length limit is for '
                    + ' style purposes, but we don't want it to have a performance impact.'
                    + ' So the question is how should we do the wrapping?';

比起用連接的方式,嘗試使用 join():
var veryLongMessage = ['This is a long string that due to our strict line length limit of',
                       maxCharsPerLine,
                       ' characters per line must be wrapped. ',
                       percentWhoDislike,
                       '% of engineers dislike this rule. The line length limit is for ',
                       ' style purposes, but we don't want it to have a performance impact.',
                       ' So the question is how should we do the wrapping?'].join();

相似的,用連接的方式在條件語句或循環中構建字符串是很低效的(內建方法優先使用)。
錯誤的方式:
    var fibonacciStr = "前 20 個斐波那契數:/n";
    for (var i = 0; i < 20; i++) {
        fibonacciStr += i + ' = ' + fibonacci(i) + '';
    }

正確的方法:
    var strBuilder = ["前 20 個斐波那契數:/n"];
    for (var i = 0; i < 20; i++) {
        strBuilder.push(i, ' = ', fibonacci(i));
    }
    var fibonacciStr = strBuilder.join('');


構建通過輔助函數生成的字符串
    通過傳遞字符串構建器(可以是數組或者輔助類)到函數中構建長字符串,避免出現存放臨時結果 的字符串。

例如,假定 buildMenuItemHtml_ 需要用文字串和變量構建一個字符串,並且會在內部使用一個字符串構建器,
與其使用:
    var strBuilder = [];
    for (var i = 0; i < menuItems.length; i++) {
        strBuilder.push(this.buildMenuItemHtml_(menuItems[i]));
    }
    var menuHtml = strBuilder.join();

不如用:
    var strBuilder = [];
    for (var i = 0; i < menuItems.length; i++) {
        this.buildMenuItem_(menuItems[i], strBuilder);
    }
    var menuHtml = strBuilder.join();


定義類的方法
    下面的代碼效率不高,因爲每次構造 baz.Bar 的實例時,都會爲 foo 創建一個新函數和閉包(closure):

    baz.Bar = function() {
        // 構造函數代碼
        this.foo = function() {
        // 方法代碼
        };
    }

推薦的方式爲:
    baz.Bar = function() {
      // 構造函數代碼
    };

    baz.Bar.prototype.foo = function() {
      // 方法代碼
    };
用這種方式,無論構造了多少個 baz.Bar 實例,只會創建一個函數給 foo,同時不會創建任何閉包。


初始化實例變量
    將帶有值類型(非引用的)的初始化值(例如類型爲數字、布爾值、null、undefined 或 字符串的值)的變量聲明/初始化代碼直接放在 prototype 原型中。這可以避免每次調用構造函數時不必要地運行初始化代碼(這個方法無法應用到初始化值由構造器參數決定或構造時狀態不確定的實例變量上)。

例如,比起寫:
    foo.Bar = function() {
        this.prop1_ = 4;
        this.prop2_ = true;
        this.prop3_ = [];
        this.prop4_ = 'blah';
    };

不如寫:
    foo.Bar = function() {
        this.prop3_ = [];
    };
    
    foo.Bar.prototype.prop1_ = 4;
    foo.Bar.prototype.prop2_ = true;
    foo.Bar.prototype.prop4_ = 'blah';


謹慎地使用閉包(closure)
    閉包是 JavaScript 中一個強大而有用的特性; 但是,它們也有不好的地方,包括:

    * 它們是最常見的內存泄漏源頭。

    * 創建一個閉包比創建一個沒有閉包的內聯函數明顯要慢,比起重用一個靜態函數則更慢. 例如:
      function setupAlertTimeout() {
          var msg = '要顯示的消息';
          window.setTimeout(function() { alert(msg); }, 100);
      }

      比下面的代碼慢:
      function setupAlertTimeout() {
          window.setTimeout(function() {
              var msg = '要顯示的消息';
              alert(msg);
          }, 100);
      }

      更比下面的代碼慢:
      function alertMsg() {
          var msg = '要顯示的消息';
          alert(msg);
      }

      function setupAlertTimeout() {
          window.setTimeout(alertMsg, 100);
      }

    * 他們增加了作用域鏈(scope chain)的層級。當瀏覽器解析屬性時,作用域鏈的每一個層級都必須被檢查一次。在下面的例子中:
      var a = 'a';

      function createFunctionWithClosure() {
          var b = 'b';
          return function () {
              var c = 'c';
              a;
              b;
              c;
          };
      }

      var f = createFunctionWithClosure();
      f();

    當 f 被調用時,引用 a 比引用 b 慢,它們都比引用 c 要慢。
    http://blogs.msdn.com/ie/archive/2007/01/04/ie-jscript-performance-recommendations-part-3-javascript-code-inefficiencies.aspx


避免使用 with
    避免使用 with。它對性能有非常壞的影響,因爲它修改了作用域鏈,讓查找在其它作用域的變量變得代價高昂。


避免瀏覽器內存泄漏
    內存泄漏對 Web 應用而言是個很普遍的問題,會帶來嚴重的性能問題。
    常見的內存泄漏原因是:在 JS 腳本引擎和瀏覽器 DOM 的 C++ 對象實現間的循環引用(例如,在 JS 腳本引擎和 IE 的 COM 基礎架構間,或者 JavaScript 引擎和 Firefox 的 XPCOM 基礎架構間)。
    下面是避免內存泄漏的一些經驗法則:
        1. 使用一個事件系統來附加事件處理函數
            最常見的循環引用模式 [ DOM 元素 --> 事件處理函數 --> 閉包作用域 --> DOM ] (見:http://blogs.msdn.com/ericlippert/archive/2003/09/17/53028.aspx)。爲避免這個問題,可以使用一個經過嚴格測試的事件系統來附加事件處理函數,例如 Google doctype,Dojo,JQuery。
            另外,在 IE 中使用內聯(inline)的事件處理函數會導致另外一類泄漏。這不是通常的循環引用泄漏,而是內存中臨時匿名腳本對象的泄漏。詳情請查看 "Understanding and Solving IE Leak Patterns" 的 "DOM Insertion Order Leak Model" 一節(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ietechcol/dnwebgen/ie_leak_patterns.asp ),另外在 JavaScript Kit 教程 中還有一個例子(http://www.javascriptkit.com/javatutors/closuresleak/index.shtml )。

        2. 避免使用擴展(expando)屬性
            擴展屬性是附加到 DOM 元素上的任意 JavaScript 屬性,也是循環引用的常見原因。你能夠在使用擴展屬性時不導致內存泄漏,但是很容易不小心就引入一個泄漏。這個泄漏的模式是 [ DOM 元素 --> 擴展屬性 --> 中間對象 --> DOM 元素 ]。
            最好的方法就是避免使用它們。如果一定要使用,就只使用簡單的值類型。如果要使用非簡單的類型,那麼在不再需要擴展屬性的時候,將它設爲空(null)。
            參見"Understanding and Solving IE Leak Patterns" 中的 "Circular References" 一節(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ietechcol/dnwebgen/ie_leak_patterns.asp )。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章