Javascript闭包 - 有什么负面消息?

时间:2010-06-17 02:31:36

标签: javascript closures

问题: 闭包似乎有很多好处,但是什么是负面的(内存泄漏?混淆问题?带宽增加?)?另外,我对闭包的理解是否正确?最后,一旦创建了闭包,它们会被销毁吗?

我一直在阅读有关Javascript Closures的一些信息。我希望有一点知识渊博的人会指导我的断言,纠正错误的地方。

关闭的好处:

  1. 使用内部函数将变量封装到本地作用域。 这个函数的匿名性是微不足道的。
  2. 我发现有用的是做一些关于本地/全球范围的基本测试:

    <script type="text/javascript">
    
       var global_text  = "";
       var global_count = 0;
       var global_num1  = 10;
       var global_num2  = 20;
       var global_num3  = 30;
    
       function outerFunc() {
    
          var local_count = local_count || 0;
    
          alert("global_num1: " + global_num1);    // global_num1: undefined
          var global_num1  = global_num1 || 0;
          alert("global_num1: " + global_num1);    // global_num1: 0
    
          alert("global_num2: " + global_num2);    // global_num2: 20
          global_num2  = global_num2 || 0;         // (notice) no definition with 'var'
          alert("global_num2: " + global_num2);    // global_num2: 20
          global_num2  = 0;
    
          alert("local_count: " + local_count);    // local_count: 0
    
          function output() {
             global_num3++;
    
             alert("local_count:  " + local_count  + "\n" +
                   "global_count: " + global_count + "\n" +
                   "global_text:  " + global_text
                  );
    
             local_count++; 
          }
    
          local_count++;
          global_count++;
    
          return output;  
       }  
    
       var myFunc = outerFunc();
    
       myFunc();
          /* Outputs:
           **********************
           * local_count:  1
           * global_count: 1
           * global_text: 
           **********************/
    
       global_text = "global";
       myFunc();
          /* Outputs:
           **********************
           * local_count:  2
           * global_count: 1
           * global_text:  global
           **********************/
    
       var local_count = 100;
       myFunc();
          /* Outputs:
           **********************
           * local_count:  3
           * global_count: 1
           * global_text:  global
           **********************/
    
    
       alert("global_num1: " + global_num1);      // global_num1: 10
       alert("global_num2: " + global_num2);      // global_num2: 0
       alert("global_num3: " + global_num3);      // global_num3: 33
    
    </script>
    

    我从中获取了有趣的东西:

    1. outerFunc中的警报只调用一次,即将outerFunc调用分配给myFunc(myFunc = outerFunc())。这个赋值似乎保持outerFunc打开,我想称之为持久状态。

    2. 每次调用myFunc时,都会执行返回。在这种情况下,返回是内部函数。

    3. 真正有趣的是定义局部变量时发生的本地化。注意global_num1和global_num2之间的第一个警报的差异,即使在尝试创建变量之前,global_num1也被认为是未定义的,因为'var'用于表示该函数的局部变量。 - 之前已经讨论过这个问题,按照Javascript引擎的操作顺序,很高兴看到它投入使用。

    4. 仍然可以使用Globals,但局部变量会覆盖它们。注意在第三次myFunc调用之前,创建了一个名为local_count的全局变量,但它对内部函数没有影响,内部函数有一个同名的变量。相反,每个函数调用都能够修改全局变量,正如global_var3所注意到的那样。

    5. 发表想法: 尽管代码很简单,但是对于你们来说警报很混乱,所以你可以即插即用。

      我知道还有其他闭包的例子,其中很多都使用匿名函数和循环结构,但我认为这对101启动课程来说很有效。

      我关注的一件事是关闭对内存产生的负面影响。因为它使函数环境保持打开,所以它还将这些变量保存在内存中,这可能/可能没有性能影响,尤其是关于DOM遍历和垃圾收集。我也不确定这会在内存泄漏方面发挥什么样的作用,我不确定是否可以通过简单的“删除myFunc;”从内存中删除闭包。

      希望这有助于某人,

      vol7ron

3 个答案:

答案 0 :(得分:6)

你可能会得到很多好的答案。一个肯定的是Internet Explorer循环引用内存泄漏。基本上,JScript不会将对DOM对象的“循环”引用识别为可收集的。使用闭包创建IE认为是循环引用很容易。第二个链接提供了几个例子。

在IE6中,回收内存的唯一方法是终止整个过程。在IE7中,他们对其进行了改进,以便当您离开相关页面(或关闭它)时,内存将被回收。在IE8中,JScript可以更好地理解DOM对象,并按照您的预期收集它们。

IE6建议的解决方法(除了终止进程!)不使用闭包。

答案 1 :(得分:6)

闭包带来了很多好处......但也有很多问题。使它们变得强大的同样的事情也使它们能够在你不小心的情况下弄得一团糟。

除了循环引用的问题(由于IE6在中国以外的地方很难使用,因此不再那么多了),至少还有一个其他巨大的潜在负面因素:它们会使范围复杂化如果使用得当,它们可以通过允许函数共享数据而不暴露它来提高模块性和兼容性......但是如果使用不当,很难准确地追踪变量的设置或更改位置。 / p>

没有闭包的JavaScript有三个变量的 * 范围:块级,功能级和全局。没有对象级范围。如果没有闭包,你知道变量要么在当前函数中声明,要么在全局对象中声明(因为那是全局变量所在的位置)。

关闭,你不再有这种保证。每个嵌套函数都引入了另一个范围,并且在该函数中创建的任何闭包都会看到( )与包含函数相同的变量。最大的问题是每个函数都可以随意定义自己的变量来隐藏外部变量。

正确使用闭包要求您(a)了解闭包和var如何影响范围,以及(b)跟踪变量所在的范围。否则,变量可能会被意外共享(或伪 - 变量丢失!),随之而来的是各种各样的古怪。


考虑这个例子:

function ScopeIssues(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        funcs[i] = function() { console.log(i); }
    }
    return funcs;
}

简短,直截了当......几乎肯定会破碎。观看:

x = ScopeIssues(100);

x[0]();   // outputs 100
x[1]();   // does too
x[2]();   // same here
x[3]();   // guess

数组中的每个函数都输出count。这里发生了什么?您正在看到将闭包与对封闭变量和范围的误解相结合的影响。

创建闭包时,他们在创建闭包时不使用i的值来确定要输出的内容。他们正在使用变量 i,它与外部函数共享并且仍在变化。当他们输出它时,他们输出的值是被称为的时间。这将等于count,这是导致循环停止的值。

为了解决这个问题,你需要另一个闭包。

function Corrected(count) {
    var funcs = [];
    for (var i = 0; i < count; ++i) {
        (function(which) {
            funcs[i] = function() { console.log(which); };
        })(i);
    }
}

x = Corrected(100);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3

另一个例子:

value = 'global variable';

function A() {
    var value = 'local variable';
    this.value = 'instance variable';
    (function() { console.log(this.value); })();
}

a = new A();  // outputs 'global variable'

thisarguments不同;几乎与其他所有事物不同,它们在闭合边界之间共享 。每个函数调用都重新定义它们 - 除非你调用函数

  • obj.func(...)
  • func.call(obj, ...)
  • func.apply(obj, [...])
  • var obj_func = func.bind(obj); obj_func(...)

指定this,然后您将获得this的默认值:全局对象。 ^

解决this问题最常见的习惯是声明变量并将其值设置为this。我见过的最常见的名字是thatself

function A() {
    var self = this;
    this.value = 'some value';
    (function() { console.log(self.value); })();
}

但这使得self成为一个真正的变量,带来所有潜在的奇怪现象。幸运的是,很少想要在不重新定义变量的情况下更改self的值...但是在嵌套函数中,重新定义self当然也会为嵌套在其中的所有函数重新定义它。你不能做像

这样的事情
function X() {
    var self = this;
    var Y = function() {
        var outer = self;
        var self = this;
    };
}

因为提升。 JavaScript有效地将所有变量声明移动到函数的顶部。这使得上面的代码等同于

function X() {
    var self, Y;
    self = this;
    Y = function() {
        var outer, self;
        outer = self;
        self = this;
    };
}
self运行之前,

outer = self已经是一个局部变量,因此outer获取本地值 - 此时此值为undefined。您刚刚丢失了对外self的引用。


*自ES7起。以前只有两个,变量甚至更容易追踪。 :P

?使用lambda语法声明的函数(ES7的新增功能)不会重新定义thisarguments。这可能使问题复杂化。

^较新的解释器支持所谓的“严格模式”:一种选择加入功能,旨在使某些不确定的代码模式完全失败或造成更少的损害。在严格模式下,this默认为undefined而不是全局对象。但它仍然是你通常打算搞砸的其他一些价值。

答案 2 :(得分:0)

闭包可能会导致内存泄漏,但Mozilla已尝试优化其垃圾收集引擎以防止此情况发生。

我不确定Chrome如何处理关闭。我认为他们与Mozilla相提并论,但我不想肯定地说。 IE8肯定比早期版本的IE改进了 - 它几乎是一个全新的浏览器,仍然有一些细微差别。

您还应该对代码进行基准测试,以确定速度是否有任何改进。

相关问题