Javascript 闭包 - 有哪些负面影响?

发布于 2024-09-05 23:28:51 字数 3055 浏览 3 评论 0原文

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

我读了一些关于 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时,都会执行return。在本例中,返回值是内部函数。

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

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

发表想法: 尽管代码很简单,但它对你们来说却充满了警报,因此您可以即插即用。

我知道还有其他闭包的示例,其中许多使用匿名函数与循环结构相结合,但我认为这对于 101 初学者课程来说有利于看到效果。

我担心的一件事是闭包会对内存产生负面影响。因为它保持函数环境打开,所以它也将这些变量存储在内存中,这可能/可能不会影响性能,特别是在 DOM 遍历和垃圾收集方面。我也不确定这在内存泄漏方面会起到什么样的作用,并且我不确定是否可以通过简单的“delete myFunc;”从内存中删除闭包。

希望这对某人有帮助,

vol7ron

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. 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()). This assignment seems to keep the outerFunc open, in what I would like to call a persistent state.

  2. Everytime myFunc is called, the return is executed. 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. -- 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.

  4. Globals can still be used, but local variables will override them. 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. Conversely, each function call has the ability to modify global variables, as noticed by 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.

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. 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;."

Hope this helps someone,

vol7ron

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

如果没有你 2024-09-12 23:28:51

关闭带来了很多好处……但也有一些陷阱。使它们变得强大的同时,如果你不小心的话,它们也很可能造成混乱。

除了循环引用问题(这实际上不再是一个问题,因为 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(10);

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

数组中的每个函数都会输出 count。这是怎么回事?您会看到将闭包与对封闭变量和作用域的误解相结合所产生的影响。

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

要在 let 存在之前解决此问题,您需要另一个闭包。

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

从 ES7 开始,您可以使用 let 代替 var,并且循环的每次迭代基本上都会获得其自己的 i 版本。

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

但这也带来了其自身的复杂性——在同一代码块中具有相同名称和用途的变量现在实际上已断开连接。所以你也不希望总是使用 let 。唯一真正的解决办法是全方位地更加了解范围。


另一个例子:

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

outer = self 运行之前 self 已经是一个局部变量,因此 outer 获取了局部值 - 这在此时,未定义。您刚刚失去了对外部自我的引用。


* 从 ES7 开始。以前只有两个,而且变量更容易追踪。 :P

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

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

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


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(10);

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

Every function in the array outputs 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. They're using the variable i, which is shared with the outer function and is still changing. 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.

To fix this before let existed, you'd need another closure.

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

As of ES7, you can use let instead of var, and each iteration of the loop will basically get its own version of i.

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

But that comes with complications of its own -- variables with the same name and purpose, in the same block of code, are now effectively disconnected. So you don't want to just always use let either. The only real fix is to all-around be much more aware of scope.


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; 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(...),
  • func.call(obj, ...),
  • func.apply(obj, [...]), or
  • var obj_func = func.bind(obj); obj_func(...)

to specify a this, then you'll get the default value for this: the global object.^

The most common idiom to get around the this issue is to declare a variable and set its value to this. The most common names i've seen are that and self.

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. 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. 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. 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. You've just lost your reference to the outer self.


* As of ES7. Previously, there were only two, and variables were even easier to track down. :P

? Functions declared using lambda syntax (new to ES7) don't redefine this and arguments. 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. But it's still some whole other value than you usually intended to mess with.

听不够的曲调 2024-09-12 23:28:51

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

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

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

You may get a raft of good answers. One certain negative is the Internet Explorer circular reference memory leak. Basically, "circular" references to DOM objects are not recognized as collectible by JScript. It's easy to create what IE considers a circular reference using closures. Several examples are provided in the second link.

In IE6, the only way to reclaim the memory is to terminate the whole process. In IE7 they improved it so that when you navigate away from the page in question (or close it), the memory is reclaimed. In IE8, DOM objects are better understood by JScript and are collected as you'd expect they should be.

The suggested workaround for IE6 (besides terminating the process!) is not to use closures.

南城追梦 2024-09-12 23:28:51

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

我不确定 Chrome 如何处理关闭。我认为它们与 Mozilla 相当,但我不想肯定地说。 IE8 相对于早期版本的 IE 无疑有所改进 - 它几乎是一个全新的浏览器,但仍然存在一些细微差别。

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

Closures may cause memory leaks, however Mozilla has made attempts to optimize their garbage collection engine to prevent this.

I'm unsure how Chrome handles closures. I think they're on par with Mozilla, but I don't want to say for sure. IE8 is definitely improved over the earlier versions of IE - it's almost a whole new browser, there are still some nuances.

You should also benchmark the code to see if there's any improvement in speed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文