Javascript - 谨慎使用闭包?

时间:2012-03-19 00:56:42

标签: javascript

我正在观看http://www.youtube.com/watch?v=mHtdZgou0qU,并且在大约13:37(嘿嘿),他显示了由于在范围链上添加了新对象而要避免的事项列表的幻灯片。

我理解他对usingtry-catch语句所说的内容,以及访问范围外的变量,但我不明白为什么应该避免使用闭包。如果闭包的局部变量将位于范围链的顶部,那么性能损失在哪里?

2 个答案:

答案 0 :(得分:20)

这是因为,要查找非本地变量,VM必须走向范围链才能找到它们。另一方面,局部变量被缓存,因此局部变量查找要快得多。函数嵌套越多,范围链变得越长,潜在的性能影响就越大。

这就是为什么你经常在流行的JS库中看到这样的代码的原因:

(function(window, document, undefined) {
  // ...
})(window, document);

在这里,windowdocument成为局部变量,因此查找它们会变得更快,如果您在代码中引用这些对象数千次,这就变得非常明显。

This page对范围链和执行上下文有一个非常深入的描述。 (如果您有时间阅读,整篇文章都很有趣。)

总的来说,现代浏览器优化所有这些东西到基本上可以忽略不计的程度,所以这不是我会担心的。

答案 1 :(得分:9)

The linked video explains why closure can inflict some performance hits starting about 11:08.

基本上,他说对于每个嵌套函数,它会向作用域链添加另一个对象,因此访问闭包之外的变量将花费更长的时间。

要查找与变量关联的值,Javascript解释器将遵循以下过程:

  1. 搜索本地范围对象
  2. 如果1不起作用,请搜索父作用域对象
  3. 如果2不起作用,请搜索父母的父作用域对象
  4. 继续搜索父作用域
  5. 您搜索全局范围
  6. 如果仍未找到,则抛出未定义的变量错误。
  7. 在正常功能中,要查找变量,您只需在范围链的顶部进行搜索。另一方面,关闭查找父变量的闭包必须向下搜索范围链,有时需要深入几个级别。例如,如果你有一些像这样的闭包(注意这是一个非常人为的例子):

    function a (x) {
      function b (y) {
        return (function (z) {
          return x + y + z;
        })(y + y);
      }
      return b(x + 3);
    }
    

    从最里面的函数,为了评估表达式x + y + z,它必须在范围链中遍历三个级别才能找到x,然后它必须再次遍历范围链两个级别才能找到y,并且然后终于找到了z。总的来说,它必须在范围链中搜索六个对象以返回最终结果。

    这在闭包中是不可避免的,因为闭包始终必须访问父变量。如果没有,那么使用封闭就没有用处了。

    另请注意,在Javascript中,创建函数(尤其是闭包)会产生很大的开销。例如,这个相当简单的闭包:

    function a(x) {
      return function (y) {
        return x + y;
      }
    }
    

    你可以在几个不同的时间称呼它,比如

    var x = a(1);
    var y = a(2);
    var z = a(3);
    alert(x(3)); // 4
    alert(y(3)); // 5
    alert(z(3)); // 6
    

    你会注意到a返回的函数必须保留在父函数中作为参数传递的内容,即使已经调用了父函数。这意味着解释器必须在内存中保存您传入到目前为止所调用的每个函数的内容。