优化 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 )。

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