对JavaScript中闭包的深入理解

deeper understanding of closure in Javascript

我读了一个答案上的评论,看到了这个评论:

[the closure] doesn't persist the state of foo so much as creates a special scope containing (1) the returned function and (2) all the external variables referenced at the time of the return. This special scope is called a closure.

好的,到目前为止还不错。下面是我不知道的有趣的部分:

Case in point... if you had another var defined in foo that was not referenced in the return function, it would not exist in the closure scope.

我想这是有道理的,但除了记忆的使用/表现,这还有什么意义呢?

问题——如果范围中的所有变量都包含在闭包中,那么这会让我做些什么,而我不能用当前模型做这些呢?


我觉得你太直截了当了。注释只是说您不能在函数范围之外访问它(它不是公共访问的),而不是说它在函数中根本不可用。返回的函数将可以访问所有外部函数范围,无论是什么。如果内部函数不提供访问范围的方法,您就不能访问外部函数之外的范围。

例如,此表达式的计算结果为4:

1
2
3
4
5
6
7
8
9
10
11
function testClosure(){
 var x = 2;
    return function(y){
        alert(eval(y));
    }

}

var closure = testClosure();

closure("x+2");  //4

http://jsfiddle.net/dmrch/

因此,尽管没有直接引用,但x仍然可用。

进一步研究

似乎Chrome和Firefox至少尝试过对此进行优化,因为如果您没有提供任何引用x变量的方法,那么它在调试器中就不会显示为可用。在一个闭包内使用断点运行这个命令,会显示在chrome26和firefox 18上x是不可用的。

http://jsfiddle.net/fgekx/1/

但这只是一个内存管理细节,而不是语言的相关属性。如果有任何可能的方法可以引用这个变量,它就会被传递,我怀疑其他浏览器可能不会以同样的方式优化这个变量。编码到规范总比实现好。在这种情况下,尽管规则实际上是:"如果您有任何可能的方法访问它,它将是可用的"。另外,不要使用eval,因为它确实会阻止代码优化任何东西。


if you had another var defined in foo that was not referenced in the return function, it would not exist in the closure scope.

这并不完全准确;变量是闭包范围的一部分,即使它可能不会直接在函数内部引用(通过查看函数代码)。区别在于引擎如何优化未使用的变量。

例如,当您使用DOM元素时,闭包范围中未使用的变量会导致内存泄漏(在某些引擎上)。以这个经典的例子为例:

1
2
3
4
5
6
function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}

来源

在上面的代码中,内存泄漏(至少在IE和Mozilla中),因为el是click handler函数的关闭范围的一部分,即使它没有被引用;这会导致循环引用无法删除,因为它们的垃圾收集基于引用计数。

另一方面,Chrome使用不同的垃圾收集器:

In V8, the object heap is segmented into two parts: new space where objects are created, and old space to which objects surviving a garbage collection cycle are promoted. If an object is moved in a garbage collection cycle, V8 updates all pointers to the object.

这也被称为世代或短暂的垃圾收集器。尽管更复杂,但这种类型的垃圾收集器可以更准确地确定是否使用变量。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
    Please have a look below code:

    for(var i=0; i< 5; i++){            
                setTimeout(function(){
                    console.log(i);
                },1000);                        
            }

   Here what will be output? 0,1,2,3,4 not that will be 5,5,5,5,5 because of closure

   So how it will solve? Answer is below:

   for(var i=0; i< 5; i++){
            (function(j){     //using IIFE          
                setTimeout(function(){
                    console.log(j);
                },1000);
            })(i);          
        }

    Let me simple explain, when a function created nothing happen until it called so for loop in 1st code called 5 times but not called immediately so when it called i.e after 1 second and also this is asynchronous so before this for loop finished and store value 5 in var i and finally execute setTimeout function five time and print 5,5,5,5,5

Here how it solve using IIFE i.e Immediate Invoking Function Expression

   (function(j){  //i is passed here          
                setTimeout(function(){
                    console.log(j);
                },1000);
            })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

For more, please understand execution context to understand closure.

- There is one more solution to solve this using let (ES6 feature) but under the hood above function is worked

 for(let i=0; i< 5; i++){          
                setTimeout(function(){
                    console.log(i);
                },1000);                        
            }

Output: 0,1,2,3,4

JavaScript没有固有的隐私意识,所以我们使用函数范围(闭包)来模拟这个功能。

您所参考的So Answer是模块模式的一个例子,Addy Osmani很好地解释了学习JavaScript设计模式的重要性:

The Module pattern encapsulates"privacy", state and organization
using closures. It provides a way of wrapping a mix of public and
private methods and variables, protecting pieces from leaking into the
global scope and accidentally colliding with another developer's
interface. With this pattern, only a public API is returned, keeping
everything else within the closure private.