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

vol*_*ron 10 javascript closures

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

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

闭包的好处:

  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>
Run Code Online (Sandbox Code Playgroud)

我从中拿出有趣的东西:

  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所注意到的那样.

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

我知道还有其他闭包的例子,其中许多使用匿名函数与循环结构相结合,但我认为这对于101启动课程来看效果是好的.

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

希望这有助于某人,

vol7ron

Ken*_*ler 6

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

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

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


cHa*_*Hao 6

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

除了循环引用的问题(这不再是一个问题,因为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;
}
Run Code Online (Sandbox Code Playgroud)

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

x = ScopeIssues(10);

x[0]();   // outputs 10
x[1]();   // does too
x[2]();   // same here
x[3]();   // guess
Run Code Online (Sandbox Code Playgroud)

数组中的每个函数都输出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);
    }
    return funcs;
}

x = Corrected(10);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3
Run Code Online (Sandbox Code Playgroud)

另一个例子:

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

x = WorksToo(10);

x[0]();  // outputs 0
x[1]();  // outputs 1
x[2]();  // outputs 2
x[3]();  // outputs 3
Run Code Online (Sandbox Code Playgroud)

let并且let是不同的; 几乎与其他所有东西不同,它们不是在封闭边界上共享的.每个函数调用都重新定义它们 - 除非你调用函数

  • var,
  • i,
  • let, 要么
  • this

要指定a arguments,那么您将获得以下内容的默认值obj.func(...):全局对象.^

绕过这个func.call(obj, ...)问题最常见的习惯是声明一个变量并将其值设置为func.apply(obj, [...]).我见过的最常见的名字是var obj_func = func.bind(obj); obj_func(...)this.

value = 'global variable';

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

a = new A();  // outputs 'global variable'
Run Code Online (Sandbox Code Playgroud)

但这是this一个真正的变量,具有所有潜在的奇怪性.幸运的是,很少想要在this不重新定义变量的情况下更改值...但是在嵌套函数中,重新定义this当然也会为嵌套在其中的所有函数重新定义它.你做不了类似的事情

function A() {
    var self = this;
    this.value = 'some value';
    (function() { console.log(self.value); })();
}
Run Code Online (Sandbox Code Playgroud)

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

function X() {
    var self = this;
    var Y = function() {
        var outer = self;
        var self = this;
    };
}
Run Code Online (Sandbox Code Playgroud)

thatself运行之前已经是一个局部变量,因此self获取本地值 - 此时是self.你刚刚丢失了对外部的引用self.


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

?使用lambda语法声明的函数(ES7的新增功能)不会重新定义selfouter = self.这可能使问题更加复杂化.

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