当前位置:嗨网首页>书籍在线阅读

55-讨论

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

5.13.3 讨论

闭包是个极好的东西。它们能轻而易举地实现状态信息的捕捉,并将这些信息传递给事件处理程序或者定时器回调等异步函数。如果JavaScript没有闭包,所有异步回调都需要有传递状态的手段。而现在你只需使用嵌套函数。

JavaScript的动态特性也是很好的。可以在任何时候为任何对象添加属性和方法,JavaScript运行时在必要的时候能够轻松地找到这些引用。

综合上述技术,你可能需要许多名称查找。

注意

最现代化的JavaScript解释程序在这一领域已经有了很大的改进。但是如果你想在最流行的浏览器(例如任何版本的IE)中快速运行代码,仍然需要注意名称查找的次数。

考虑如下的代码:

//获得局部作用域的典型函数包装器
(function() {
   //查找一组数值中最大的绝对值
   function maxMagnitude( array ) {
     var largest = -Infinity;
     $.each( array, function() {
        largest = Math.max( largest, Math.abs(this) );
     });
   return largest;
  }
   //这里放置在大数组上调用maxMagnitude的其他代码
})();

记住,JavaScript首先在局部作用域(函数)中查找,如果没有找到名称,就查找上一级嵌套函数,直至全局作用域。JavaScript不仅在每次使用名称时需要查找这些名称,而且在名称实际上定义于上级函数或者全局作用域中时也必须重复这些查找。

所以,如果这个代码块在全局作用域中,each()回调在每次循环中都要进行如下的名称查找:

1.在局部作用域中查找largest[失败]
2.在MaxMagnitude()中查找largest[成功]
3.在局部作用域中查找Math[失败]
4.在MaxMagnitude()中查找Math[失败]
5.在匿名包装器函数中查找Math[失败]
6.在全局作用域中查找Math[成功]
7.在Math对象中查找.abs[成功]
8.在局部作用域中查找Math[失败]
9.在MaxMagnitude()中查找Math[失败]
10.在匿名包装器函数中查找Math[失败]
11.在全局作用域中查找Math[成功]
12.在Math对象中查找max[成功]
13.在局部作用域中查找largest[失败]
14.在MaxMagnitude()中查找largest[成功]

现在将代码重写为:

//获得局部作用域的典型函数包装器
(function() {
   //查找一组数值中最大的绝对值
   function maxMagnitude( array ) {
     var abs = Math.abs, max = Math.max;
     var largest = -Infinity;
     for( var i = −1, n = array.length; ++i < n; ) {
        largest = max( largest, abs(array[i]) );
     }
     return largest;
   }
   //这里放置在大数组上调用maxMagnitude的其他代码
})();

新的代码不仅消除了每次循环中对回调函数的调用,而且将每次循环中的名称查找减少了至少10次。在这一版本的循环体中进行如下的名称查找:

1.在局部作用域中查找largest[成功]
2.在局部作用域中查找abs[成功]
3.在局部作用域中查找max[成功]
4.在局部作用域中查找largest[成功]

这比第一个版本的性能增强了70%以上。

如果这段代码更深地嵌套到另一个函数内,差异可能更大,因为每个嵌套函数都会增加一次对 Math 对象的查找。

注意

在上面的讨论中,省略了 thisarray[i] 的查找,以及 for 循环本身的查找。这些查找在两个版本中基本相同。

在秘诀5.11中,单个名称查找的优化就能带来100毫秒的改进。这虽然不是巨大的差异,但是对于一行代码来说,能够减少1/10秒的页面加载时间,就已经很有价值了。

原来的代码在每次循环中调用 esc() 6次,在1000个名字的测试用例中一共要调用6000次。这些调用位于3个嵌套函数内部,而 esc() 是一个全局函数,所以仅为了解析函数名称,每次调用就要进行4次名称查找,一共就是24 000次!

改进的代码将函数嵌套减少了一级,从而将名称查找减少到18 000次(两级嵌套函数和全局作用域,每一级6000次),然后还在最内层的函数中使用了最后一种技巧:

function fillTable( names ) {
  var e = esc;
   //现在在内部循环调用e()代替esc()
}

现在,对 e() 的6000次调用,每次只需要进行一次名称查找,又减少了12 000次名称查找。难怪它减少了1/10秒的加载时间。