代码之家  ›  专栏  ›  技术社区  ›  vol7ron

JavaScript闭包-什么是否定的?

  •  10
  • vol7ron  · 技术社区  · 15 年前

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

    我读过一些关于JavaScript闭包的文章。我希望有一个知识多一点的人来指导我的主张,纠正我哪里错了。

    关闭的好处:

    1. 使用内部函数将变量封装到本地范围。 函数的匿名性是微不足道的。

    我发现有帮助的是做一些关于本地/全球范围的基本测试:

    <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. 真正有趣的是定义局部变量时发生的本地化。请注意,第一个警报中全局数字1和全局数字2之间的区别,即使在尝试创建变量之前,全局数字1仍被视为未定义,因为“var”用于表示该函数的局部变量。--这在前面已经讨论过了,按照JavaScript引擎的操作顺序,很高兴看到这项功能开始工作。

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

    后思想: 尽管代码很简单,但它却被提醒弄得乱七八糟,所以您可以插入和播放。

    我知道还有其他闭包的例子,其中许多闭包结合循环结构使用匿名函数,但我认为这对于101入门课程来说是很好的,可以看到效果。

    我关心的一件事是闭包对内存的负面影响。因为它使函数环境保持打开状态,所以它也将这些变量存储在内存中,这可能/可能不会影响性能,特别是对于DOM遍历和垃圾收集。我也不确定这将在内存泄漏方面扮演什么样的角色,我也不确定是否可以通过简单的“delete myfunc;”从内存中删除闭包。

    希望这能帮助别人,

    沃尔森

    3 回复  |  直到 7 年前
        1
  •  6
  •   Ken Redler    15 年前

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

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

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

        2
  •  6
  •   cHao    7 年前

    关闭会带来很多好处……但也会带来很多好处。如果你不小心的话,同样的事情也会让他们变得强大,很有可能把事情搞得一团糟。

    除了循环引用的问题(这不再是真正的问题了,因为IE6在中国以外几乎没有被使用过),至少还有一个巨大的潜在负面因素: 它们会使范围复杂化。 如果使用得当,它们通过允许函数共享数据而不暴露数据来提高模块性和兼容性……但是如果使用得不好,就很难准确跟踪变量的设置或更改位置。

    没有闭包的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 在创建时,它们是为了确定要输出什么。他们用的是 变量 与外部功能共享,并且仍在变化。当他们输出它时,他们输出的是截止到现在的值。 打电话 . 等于 计数 ,导致循环停止的值。

    为了解决这个问题,您需要另一个关闭。

    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'
    

    this arguments 是不同的;与其他几乎所有的东西不同,它们是 跨越封闭边界共享 ? . 每个函数调用都会重新定义它们——除非像

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

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

    最常见的成语 问题是声明一个变量并将其值设置为 . 我见过的最常见的名字是 that self .

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

    但这使得 自己 一个真正的变量,包含所有潜在的怪事。幸运的是,很少有人想改变 自己 不重新定义变量…但在嵌套函数中,重新定义 自己 当然,也为嵌套在其中的所有函数重新定义它。你不能做这样的事

    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;
        };
    }
    

    自己 以前已经是局部变量 outer = self 运行,所以 outer 获取本地值——此时,它是 undefined . 你刚刚失去了对外部的联系 自己 .


    *ES7。以前,只有两个变量,变量更容易被跟踪。P

    ?使用lambda语法(ES7新增)声明的函数不重新定义 争论 . 这可能使事情更加复杂。

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

        3
  •  0
  •   user376314    15 年前

    关闭可能会导致内存泄漏,但是Mozilla已经尝试优化其垃圾收集引擎来防止这种情况发生。

    我不确定铬是如何处理关闭。我觉得他们和Mozilla不相上下,但我不想肯定地说。IE8肯定比早期版本的IE有所改进——它几乎是一个全新的浏览器,还有一些细微差别。

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