简体   繁体   English

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

[英]Javascript Closures - What are the negatives?

Question: There seem to be many benefits to Closures, but what are the negatives (memory leakage? obfuscation problems? bandwidth increasage?)? 问题:闭包似乎有很多好处,但是什么是负面的(内存泄漏?混淆问题?带宽增加?)? Additionally, is my understanding of Closures correct? 另外,我对闭包的理解是否正确? Finally, once closures are created, can they be destroyed? 最后,一旦创建了闭包,它们会被销毁吗?

I've been reading a little bit about Javascript Closures. 我一直在阅读有关Javascript Closures的一些信息。 I hope someone a little more knowledgeable will guide my assertions, correcting me where wrong. 我希望有一点知识渊博的人会指导我的断言,纠正错误的地方。

Benefits of Closures: 闭包的好处:

  1. Encapsulate the variables to a local scope, by using an internal function. 使用内部函数将变量封装到本地作用域。 The anonymity of the function is insignificant. 该函数的匿名性是微不足道的。

What I've found helpful is to do some basic testing, regarding local/global scope: 我发现有用的是做一些关于本地/全球范围的基本测试:

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

Interesting things I took out of it: 我从中拿出有趣的东西:

  1. The alerts in outerFunc are only called once, which is when the outerFunc call is assigned to myFunc (myFunc = outerFunc()). outerFunc中的警报只调用一次,即将outerFunc调用分配给myFunc(myFunc = outerFunc())。 This assignment seems to keep the outerFunc open, in what I would like to call a persistent state. 这个赋值似乎保持outerFunc打开,我想称之为持久状态。

  2. Everytime myFunc is called, the return is executed. 每次调用myFunc时,都会执行返回。 In this case, the return is the internal function. 在这种情况下,返回是内部函数。

  3. Something really interesting is the localization that occurs when defining local variables. 真正有趣的是定义局部变量时发生的本地化。 Notice the difference in the first alert between global_num1 and global_num2, even before the variable is trying to be created, global_num1 is considered undefined because the 'var' was used to signify a local variable to that function. 注意global_num1和global_num2之间的第一个警报的差异,即使在尝试创建变量之前,global_num1也被认为是未定义的,因为'var'用于表示该函数的局部变量。 -- This has been talked about before, in the order of operation for the Javascript engine, it's just nice to see this put to work. - 之前已经讨论过,按照Javascript引擎的操作顺序,很高兴看到这个工作正常。

  4. Globals can still be used, but local variables will override them. 仍然可以使用Globals,但局部变量将覆盖它们。 Notice before the third myFunc call, a global variable called local_count is created, but it as no effect on the internal function, which has a variable that goes by the same name. 注意在第三次myFunc调用之前,创建了一个名为local_count的全局变量,但它对内部函数没有影响,内部函数有一个同名的变量。 Conversely, each function call has the ability to modify global variables, as noticed by global_var3. 相反,每个函数调用都能够修改全局变量,正如global_var3所注意到的那样。

Post Thoughts: Even though the code is straightforward, it is cluttered by alerts for you guys, so you can plug and play. 发表想法:尽管代码很简单,但是对于你们来说它会被警报混乱,所以你可以即插即用。

I know there are other examples of closures, many of which use anonymous functions in combination with looping structures, but I think this is good for a 101-starter course to see the effects. 我知道还有其他闭包的例子,其中许多使用匿名函数与循环结构相结合,但我认为这对于101启动课程来看效果是好的。

The one thing I'm concerned with is the negative impact closures will have on memory. 我关心的一件事是关闭对内存的负面影响。 Because it keeps the function environment open, it is also keeping those variables stored in memory, which may/may not have performance implications, especially regarding DOM traversals and garbage collection. 因为它使函数环境保持打开,所以它还将这些变量保存在内存中,这可能/可能没有性能影响,尤其是关于DOM遍历和垃圾收集。 I'm also not sure what kind of role this will play in terms of memory leakage and I'm not sure if the closure can be removed from memory by a simple "delete myFunc;." 我也不确定这会在内存泄漏方面发挥什么样的作用,我不确定是否可以通过简单的“删除myFunc;”从内存中删除闭包。

Hope this helps someone, 希望这有助于某人,

vol7ron vol7ron

Closures bring a lot of benefits...but also a number of gotchas. 闭包带来了很多好处......但也有很多问题。 The same thing that makes them powerful also makes them quite capable of making a mess if you're not careful. 同样让它们变得强大的东西也使它们能够在你不小心的情况下弄得一团糟。

Besides the issue with circular references (which isn't really as much of a problem anymore, since IE6 is hardly used at all outside of China), there's at least one other huge potential negative: They can complicate scope. 除了循环引用的问题(这不再是一个问题,因为IE6在中国以外很难使用),至少还有一个巨大的潜在负面因素: 它们可能使范围复杂化。 When used well, they improve modularity and compatibility by allowing functions to share data without exposing it...but when used badly, it can become difficult if not impossible to trace exactly where a variable is set or changed. 如果使用得当,它们可以通过允许函数共享数据而不暴露它来提高模块性和兼容性......但是如果使用不当,即使不是不可能精确地追踪变量的设置或更改位置也很困难。

JavaScript without closures has three * scopes for variables: block-level, function-level, and global. 没有闭包的JavaScript有三个*变量范围:块级,功能级和全局。 There is no object-level scope. 没有对象级范围。 Without closures, you know a variable is either declared in the current function, or in the global object (because that's where global variables live). 如果没有闭包,您就知道变量要么在当前函数中声明,要么在全局对象中声明(因为这是全局变量所在的位置)。

With closures, you no longer have that assurance. 有了封闭装置,你就不再有这种保证了。 Each nested function introduces another level of scope, and any closures created within that function see ( mostly ) the same variables as the containing function does. 每个嵌套函数都引入了另一个范围的范围,在该函数中创建的任何闭包都会( 大多数 )看到与包含函数相同的变量。 The big problem is that each function can define its own variables at will that hide the outer ones. 最大的问题是每个函数都可以随意定义自己的变量来隐藏外部变量。

Using closures properly requires that you (a) be aware of how closures and var affect scope, and (b) keep track of which scope your variables are in. Otherwise, variables can be accidentally shared (or pseudo-variables lost!), and all sorts of wackiness can ensue. 正确使用闭包要求您(a)了解闭包和var如何影响范围,以及(b)跟踪变量所在的范围。否则,变量可能会被意外共享(或伪变量丢失!),以及各种古怪都可以随之而来。


Consider this example: 考虑这个例子:

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

Short, straightforward...and almost certainly broken. 简短,直截了当......几乎肯定会破碎。 Watch: 看:

x = ScopeIssues(100);

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

Every function in the array outputs count . 数组中的每个函数都输出count What's going on here? 这里发生了什么? You're seeing the effects of combining closures with a misunderstanding of closed-over variables and scope. 您将看到将闭包与对封闭变量和范围的误解相结合的影响。

When the closures are created, they're not using the value of i at the time they were created to determine what to output. 创建闭包时,他们在创建闭包时不使用i的值来确定要输出的内容。 They're using the variable i , which is shared with the outer function and is still changing. 他们使用变量 i ,它与外部函数共享并且仍在变化。 When they output it, they're outputting the value as of the time it is called . 当它们输出它时,它们输出的值就是它被调用的时间。 That will be equal to count , the value that caused the loop to stop. 这将等于count ,导致循环停止的值。

In order to fix this, you'll need another closure. 为了解决这个问题,你需要另一个闭包。

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

Another example: 另一个例子:

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 and arguments are different; thisarguments是不同的; unlike nearly everything else, they are not shared across closure boundaries ? 几乎与其他所有东西不同,它们不是在封闭边界上共享的 . Every function call redefines them -- and unless you call the function like 每个函数调用都重新定义它们 - 除非你调用函数

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

to specify a this , then you'll get the default value for this : the global object. 指定this ,那么你会得到默认值this :全局对象。 ^ ^

The most common idiom to get around the this issue is to declare a variable and set its value to this . 解决this问题最常见的习惯是声明一个变量并将其值设置this值。 The most common names i've seen are that and self . 我见过的最常见的名字是thatself

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

But that makes self a real variable, with all the potential oddness that entails. 但这使得self成为一个真正的变量,带来所有潜在的奇怪之处。 Fortunately, it's rare to want to change the value of self without redefining the variable...but within a nested function, redefining self of course redefines it for all the functions nested within it as well. 幸运的是,很少想要在不重新定义变量的情况下改变self的值......但是在嵌套函数中,重新定义self当然会为嵌套在其中的所有函数重新定义它。 And you can't do something like 你做不了类似的事情

function X() {
    var self = this;
    var Y = function() {
        var outer = self;
        var self = this;
    };
}

because of hoisting . 因为吊装 JavaScript effectively moves all the variable declarations to the top of the function. JavaScript有效地将所有变量声明移动到函数的顶部。 That makes the above code equivalent to 这使得上面的代码相当于

function X() {
    var self, Y;
    self = this;
    Y = function() {
        var outer, self;
        outer = self;
        self = this;
    };
}

self is already a local variable before outer = self runs, so outer gets the local value -- which at this point, is undefined . selfouter = self运行之前已经是一个局部变量,因此outer获取本地值 - 此时这是undefined You've just lost your reference to the outer self . 你刚刚失去了对外在self引用。


* As of ES7. *自ES7起。 Previously, there were only two, and variables were even easier to track down. 以前只有两个,变量甚至更容易追踪。 :P :P

? Functions declared using lambda syntax (new to ES7) don't redefine this and arguments . 使用lambda语法声明的函数(ES7的新增功能)不会重新定义thisarguments Which potentially complicates the matter even more. 这可能使问题更加复杂化。

^ Newer interpreters support a so-called "strict mode": an opt-in feature that aims to make certain iffy code patterns either fail entirely or cause less damage. ^较新的解释器支持所谓的“严格模式”:一种选择加入功能,旨在使某些不确定的代码模式完全失败或造成更少的损害。 In strict mode, this defaults to undefined rather than the global object. 在严格模式下, this默认为undefined ,而不是全局对象。 But it's still some whole other value than you usually intended to mess with. 但它仍然是你通常打算搞砸的其他一些价值。

You may get a raft of good answers. 你可能得到一大堆好的答案。 One certain negative is the Internet Explorer circular reference memory leak. 一个肯定的是Internet Explorer循环引用内存泄漏。 Basically, "circular" references to DOM objects are not recognized as collectible by JScript. 基本上,JScript不会将对DOM对象的“循环”引用识别为可收集的。 It's easy to create what IE considers a circular reference using closures. 使用闭包创建IE认为是循环引用很容易。 Several examples are provided in the second link. 第二个链接提供了几个示例。

In IE6, the only way to reclaim the memory is to terminate the whole process. 在IE6中,回收内存的唯一方法是终止整个过程。 In IE7 they improved it so that when you navigate away from the page in question (or close it), the memory is reclaimed. 在IE7中,他们对其进行了改进,以便当您离开相关页面(或关闭它)时,内存将被回收。 In IE8, DOM objects are better understood by JScript and are collected as you'd expect they should be. 在IE8中,JScript可以更好地理解DOM对象,并按照您的预期收集它们。

The suggested workaround for IE6 (besides terminating the process!) is not to use closures. IE6的建议解决方法(除了终止进程!)不是使用闭包。

Closures may cause memory leaks, however Mozilla has made attempts to optimize their garbage collection engine to prevent this. 闭包可能会导致内存泄漏,但Mozilla已尝试优化其垃圾收集引擎以防止此情况发生。

I'm unsure how Chrome handles closures. 我不确定Chrome如何处理关闭。 I think they're on par with Mozilla, but I don't want to say for sure. 我认为他们与Mozilla相提并论,但我不想肯定地说。 IE8 is definitely improved over the earlier versions of IE - it's almost a whole new browser, there are still some nuances. IE8肯定比早期版本的IE改进了 - 它几乎是一个全新的浏览器,仍然有一些细微差别。

You should also benchmark the code to see if there's any improvement in speed. 您还应该对代码进行基准测试,以确定速度是否有任何改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM