JavaScript闭包如何工作?

如果您知道JavaScript闭包的概念(例如函数、变量等),但不了解闭包本身,您将如何向您解释JavaScript闭包呢?

我在Wikipedia上看到过这个Scheme示例,但不幸的是它没有提供任何帮助。


JavaScript闭包初学者

Submitted by Morris on Tue, 2006-02-21 10:19. Community-edited since.

闭包不是魔法

本页解释闭包,以便程序员能够理解它们。使用可工作的JavaScript代码。它不适合大师或函数式程序员。

一旦了解了核心概念,闭包就不难理解了。然而,他们不可能通过阅读任何理论或学术取向的解释!

本文面向具有一定主流语言编程经验的程序员,并且能够阅读以下JavaScript函数:

1
2
3
4
5
6
function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

两个简短的摘要

当一个函数(foo)声明其他函数(bar和baz)时,在函数退出时不会销毁在foo中创建的局部变量。这些变量只是对外部世界变得不可见。因此,foo可以巧妙地返回函数barbaz,并且它们可以通过这个封闭的变量家族("闭包")继续相互读取、写入和通信,没有人可以干涉这些函数,即使将来有人再次调用foo也不行。

闭包是支持一流功能的一种方式;它是一个表达式,可以引用其作用域内的变量(当它第一次声明时),赋值给一个变量,作为参数传递给一个函数,或者作为函数结果返回。

闭包的一个例子

以下代码返回对函数的引用:

1
2
3
4
5
6
7
function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs"Hello Bob"

大多数JavaScript程序员将理解如何将对函数的引用返回到上面代码中的变量(say2)。如果不这样做,那么在学习闭包之前,您需要先了解它。使用C语言的程序员会认为函数返回一个指向函数的指针,而变量saysay2都是指向函数的指针。

函数的C指针和函数的JavaScript引用之间有一个关键的区别。在JavaScript中,可以将函数引用变量看作同时具有指向函数的指针和指向闭包的隐藏指针。

上面的代码有一个闭包,因为匿名函数function() { console.log(text); }是在另一个函数(本例中是sayHello2())中声明的。在JavaScript中,如果在另一个函数中使用function关键字,则创建一个闭包。

在C语言和大多数其他通用语言中,函数返回后,由于堆栈帧被销毁,所有的局部变量不再可访问。

在JavaScript中,如果在另一个函数中声明一个函数,那么外部函数的局部变量在返回后仍然可以访问。上面演示了这一点,因为我们在从sayHello2()返回后调用函数say2()。注意,我们调用的代码引用变量text,它是函数sayHello2()的一个局部变量。

1
function() { console.log(text); } // Output of say2.toString();

查看say2.toString()的输出,我们可以看到代码引用了变量text。匿名函数可以引用text,该函数保存值'Hello Bob',因为sayHello2()的局部变量在闭包中被秘密保存为活动的。

其天才之处在于,在JavaScript中,函数引用也有一个对它在&mdash中创建的闭包的秘密引用。类似于委托是方法指针加上对对象的秘密引用。

更多的例子

由于某种原因,当您阅读闭包时,似乎很难理解它们,但是当您看到一些示例时,您就会清楚它们是如何工作的(我花了一些时间)。我建议仔细研究这些示例,直到您理解它们是如何工作的。如果在没有完全理解闭包的工作原理的情况下就开始使用闭包,那么很快就会产生一些非常奇怪的bug !

示例3

这个例子表明局部变量没有被复制—他们作为参考而保存。就好像堆栈帧在外部函数退出后仍然在内存中保持活动!

1
2
3
4
5
6
7
8
9
function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

示例4

所有三个全局函数都有对同一个闭包的公共引用,因为它们都在对setupSomeGlobals()的单个调用中声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

这三个函数共享对同一个闭包的访问;定义了三个函数时setupSomeGlobals()的局部变量。

注意,在上面的示例中,如果再次调用setupSomeGlobals(),那么将创建一个新的闭包(堆栈帧!)。旧的gLogNumbergIncreaseNumbergSetNumber变量被具有新闭包的新函数覆盖。(在JavaScript中,每当在另一个函数中声明一个函数时,每次调用外部函数时都会重新创建内部函数。)

示例5

这个例子显示了闭包包含了在它退出之前在外部函数中声明的任何局部变量。注意,变量alice实际上是在匿名函数之后声明的。匿名函数首先声明,当调用该函数时,它可以访问alice变量,因为alice在同一个范围内(JavaScript执行变量提升)。另外,sayAlice()()直接调用从sayAlice()和mdash返回的函数引用;它与前面所做的完全相同,但是没有临时变量。

1
2
3
4
5
6
7
function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs"Hello Alice"

技巧:注意say变量也在闭包中,可以被sayAlice()中声明的任何其他函数访问,或者可以在内部函数中递归访问。

例子6

对于很多人来说,这是一个真正的问题,所以您需要理解它。如果要在循环中定义函数,请非常小心:闭包中的局部变量可能不像您最初认为的那样。

为了理解这个例子,您需要理解Javascript中的"变量提升"特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs"item2 undefined" 3 times

result.push( function() {console.log(item + ' ' + list[i])}行三次向结果数组中添加对匿名函数的引用。如果你不太熟悉匿名函数,可以这样想:

1
2
pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

注意,当您运行这个示例时,"item2 undefined"被记录了三次!这是因为就像前面的例子一样,对于buildList的局部变量只有一个闭包(即resultiitem)。在fnlist[j]()行调用匿名函数时;它们都使用相同的单个闭包,并且在一个闭包中使用iitem的当前值(其中i的值为3,因为循环已经完成,而item的值为'item2')。注意,我们是从0开始索引的,因此item的值为item2。i++将把i增量为3

当使用变量item的块级声明(通过let关键字)而不是通过var关键字的函数范围的变量声明时,可能会有帮助。如果进行了更改,那么数组result中的每个匿名函数都有自己的闭包;运行示例时,输出如下:

1
2
3
item0 undefined
item1 undefined
item2 undefined

如果变量i也是使用let而不是var定义的,则输出为:

1
2
3
item0 1
item1 2
item2 3

示例7

在最后一个示例中,对main函数的每个调用都创建一个单独的闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

总结

如果所有的东西看起来都完全不清楚,那么最好的方法就是使用这些例子。阅读解释要比理解例子难得多。我对闭包和栈架等的解释在技术上是不正确的。它们是旨在帮助理解的粗略简化。一旦初步了解了基本概念,以后就可以了解细节了。

最后点:

无论何时在另一个函数中使用function,都会使用闭包。无论何时在函数中使用eval(),都会使用闭包。在文本中eval可以引用函数的局部变量,在eval中甚至可以使用eval('var foo = …')创建新的局部变量当您在函数中使用new Function(…)(函数构造函数)时,它不会创建闭包。(新函数不能引用外部函数的局部变量。)JavaScript中的闭包类似于保存所有本地变量的副本,就像函数退出时一样。最好的想法是,一个闭包总是被创建为一个函数的条目,并且局部变量被添加到该闭包中。每次调用带有闭包的函数时,都会保存一组新的局部变量(假定该函数内部包含一个函数声明,并且以某种方式返回对该内部函数的引用或保存对该函数的外部引用)。两个函数可能看起来具有相同的源文本,但是由于它们的"隐藏"闭包,它们的行为完全不同。我认为JavaScript代码实际上不能发现函数引用是否有闭包。如果您试图做任何动态的源代码修改(例如:myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));),如果myFunction是一个闭包(当然,您甚至不会考虑在运行时做源代码字符串替换,但是……),那么它将不起作用。有可能在函数内部的函数声明中获得函数声明,并且您可以在多个级别上获得闭包。我认为通常闭包是函数和被捕获的变量的统称。注意,我在本文中没有使用这个定义!我怀疑JavaScript中的闭包与函数语言中的闭包不同。

使用闭包模拟对象的私有属性和私有方法。一个伟大的解释如何关闭可以导致内存泄漏在IE中,如果你不小心。

感谢

如果您刚刚学习了闭包(在这里或其他地方!),那么我对您的任何反馈都很感兴趣,您可能会建议进行任何更改,从而使本文更加清晰。发送电子邮件到morrisjohns.com (morris_closure @)。请注意,我不是JavaScript和mdash的专家;和闭包。

莫里斯的原创文章可以在互联网档案中找到。


每当您在另一个函数中看到function关键字时,内部函数就可以访问外部函数中的变量。

1
2
3
4
5
6
7
8
9
10
11
function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

这将始终记录16,因为bar可以访问被定义为foo参数的x,它还可以从foo访问tmp

这是一个结束。函数不需要返回值就可以被称为闭包。只要访问当前词法作用域之外的变量,就会创建一个闭包。

1
2
3
4
5
6
7
8
9
10
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

上面的函数也将记录16,因为bar仍然可以引用xtmp,即使它不再直接位于范围内。

但是,由于tmp仍然在bar的闭包中,所以它也在增加。它将在每次调用bar时递增。

闭包最简单的例子是:

1
2
3
4
5
6
7
8
var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

当调用JavaScript函数时,将创建一个新的执行上下文。除了函数参数和父对象外,这个执行上下文还接收在其外部声明的所有变量(在上面的示例中,包括'a'和'b')。

通过返回一个闭包函数列表或将它们设置为全局变量,可以创建多个闭包函数。所有这些都将引用相同的x和相同的tmp,它们不创建自己的副本。

这里的数字x是一个文字数字。与JavaScript中的其他文字一样,当调用foo时,将数字x复制到foo中作为参数x

另一方面,JavaScript在处理对象时总是使用引用。如果您使用一个对象调用foo,那么它返回的闭包将引用该原始对象!

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

正如预期的那样,对bar(10)的每个调用都将增加x.memb。出乎意料的是,x只是引用与age变量相同的对象!在多次调用bar之后,age.memb将是2!这种引用是HTML对象内存泄漏的基础。


引言:当问题是:

Like the old Albert said :"If you can't explain it to a six-year old, you really don't understand it yourself.". Well I tried to explain JS closures to a 27 years old friend and completely failed.

Can anybody consider that I am 6 and strangely interested in that subject ?

我很确定我是唯一一个试图从字面上理解第一个问题的人。从那以后,这个问题已经变了好几次,所以我的回答现在看来可能非常愚蠢。的地方。希望这个故事的大意对一些人来说仍然是有趣的。

我非常喜欢用类比和隐喻来解释一些难懂的概念,所以让我来试着讲个故事。

从前:

有一位公主……

1
function princess() {

她生活在一个充满冒险的奇妙世界里。她遇见了她的白马王子,骑着独角兽环游了她的世界,与龙搏斗,遇到了会说话的动物,以及许多其他幻想的事情。

1
2
3
4
5
6
7
8
9
    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel ="Hello!";

    /* ... */

但她总要回到她那充满家务和大人们的单调世界。

1
    return {

她经常给他们讲她最近作为一个公主的奇妙经历。

1
2
3
4
5
        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

但他们看到的只是一个小女孩……

1
var littleGirl = princess();

…讲述关于魔法和幻想的故事。

1
littleGirl.story();

即使大人们知道真正的公主,他们也不会相信独角兽或龙,因为他们永远看不到它们。大人们说它们只存在于小女孩的想象中。

但我们知道真正的真理;那个里面有公主的小女孩…

…真是个公主,里面有个小女孩。


认真对待这个问题,我们应该找出一个典型的6岁孩子的认知能力,尽管不可否认的是,一个对JavaScript感兴趣的孩子并不是那么典型。

关于儿童发展:5到7岁

Your child will be able to follow two-step directions. For example, if you say to your child,"Go to the kitchen and get me a trash bag" they will be able to remember that direction.

我们可以用这个例子来解释闭包,如下:

The kitchen is a closure that has a local variable, called trashBags. There is a function inside the kitchen called getTrashBag that gets one trash bag and returns it.

我们可以用JavaScript编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

进一步说明闭包为何有趣的观点:

每次调用makeKitchen()时,都会用它自己单独的trashBags创建一个新的闭包。trashBags变量是每个厨房内部的局部变量,不能在外部访问,但是getTrashBag属性上的内部函数可以访问它。每个函数调用都会创建一个闭包,但是没有必要保留闭包,除非可以从闭包外部调用一个可以访问闭包内部的内部函数。在这里使用getTrashBag函数返回对象。


稻草人

我需要知道一个按钮被点击了多少次,每点击三次就做点什么……

相当显而易见的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
1
<button id="button">Click Me!</button>

现在,这将工作,但它确实通过添加一个变量来侵占外部范围,该变量的唯一目的是跟踪计数。在某些情况下,这将是最好的,因为您的外部应用程序可能需要访问这些信息。但在本例中,我们只更改每三次单击的行为,因此最好将此功能封装在事件处理程序中。

考虑这个选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
1
<button id="button">Click Me!</button>

注意这里的一些事情。

在上面的例子中,我使用了JavaScript的闭包行为。这种行为允许任何函数无限期地访问创建它的范围。为了实际应用这一点,我立即调用一个返回另一个函数的函数,由于我返回的函数可以访问内部count变量(由于上面解释的闭包行为),因此结果函数将使用一个私有范围……没有这么简单吗?让我们稀释一下……

一个简单的单行闭包

1
2
3
4
5
6
7
//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

返回函数外部的所有变量对返回的函数都可用,但对返回的函数对象不直接可用…

1
2
func();  // Alerts"val"
func.a;  // Undefined

明白了吗?因此,在我们的主要示例中,count变量包含在闭包中,并且始终对事件处理程序可用,因此它在单击之间保持其状态。

此外,这个私有变量状态是完全可访问的,既可用于读取,也可用于为其私有作用域变量赋值。

你去;现在您已经完全封装了这个行为。

完整的博客文章(包括jQuery注意事项)


闭包很难解释,因为它们用于使某些行为起作用,而每个人都直观地期望这些行为起作用。我发现最好的解释他们的方法(以及我学到他们做什么的方法)是想象没有他们的情况:

1
2
3
4
5
6
    var bind = function(x) {
        return function(y) { return x + y; };
    }
   
    var plus5 = bind(5);
    console.log(plus5(3));

如果JavaScript不知道闭包会发生什么?只需用它的方法体替换最后一行中的调用(这基本上就是函数调用所做的),就可以得到:

1
console.log(x + 3);

现在,x的定义在哪里?我们没有在当前范围中定义它。唯一的解决方案是让plus5携带它的作用域(或者更确切地说,它的父作用域)。通过这种方式,x定义良好,并被绑定到值5。


这是为了澄清其他一些答案中出现的关于闭包的几个(可能的)误解。

闭包不仅在返回内部函数时创建。实际上,为了创建闭包,封闭函数根本不需要返回。相反,您可以将内部函数分配给外部作用域中的变量,或者将其作为参数传递给另一个函数,在该函数中可以立即调用它,也可以在以后的任何时候调用它。因此,一旦调用封闭函数,就可能创建封闭函数的闭包,因为无论何时调用内部函数,在封闭函数返回之前或之后,任何内部函数都可以访问该闭包。闭包不引用其作用域中变量的旧值的副本。变量本身是闭包的一部分,因此访问其中一个变量时看到的值是访问它时的最新值。这就是为什么在循环内部创建内部函数可能比较棘手,因为每个循环都可以访问相同的外部变量,而不是在函数创建或调用时获取变量的副本。闭包中的"变量"包括函数中声明的任何命名函数。它们还包括函数的参数。闭包还可以访问它包含的闭包变量,一直到全局范围。闭包使用内存,但它们不会导致内存泄漏,因为JavaScript本身会清理自己的未引用的循环结构。当internet资源管理器无法断开引用闭包的DOM属性值时,就会产生涉及闭包的内存泄漏,从而维护对可能的循环结构的引用。


好的,6岁的闭扇。你想听一个关于结束的最简单的例子吗?

让我们想象下一个场景:一个司机坐在车里。那辆车在一架飞机里。飞机在机场。驾驶员能够在车外,但在飞机内部,即使飞机离开机场,也会被关闭。就是这样。当你27岁时,看看更详细的解释或下面的例子。

下面是我如何将我的飞机故事转换成代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was" + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");


闭包很像一个对象。无论何时调用函数,它都会被实例化。

JavaScript中的闭包范围是词法的,这意味着闭包所属的函数中包含的所有内容都可以访问其中的任何变量。

如果需要,则在闭包中包含一个变量

使用var foo=1;或分配它只写var foo;

如果一个内部函数(包含在另一个函数中的函数)访问这样一个变量,而不使用var在自己的范围内定义它,那么它将修改外部闭包中变量的内容。

闭包比生成它的函数的运行时寿命长。如果其他函数超出了定义它们的闭包/范围(例如作为返回值),则这些函数将继续引用闭包。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

输出

1
2
3
4
somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged


不久前,我写了一篇解释闭包的博客文章。下面是我对闭包的解释。

Closures are a way to let a function
have persistent, private variables -
that is, variables that only one
function knows about, where it can
keep track of info from previous times
that it was run.

从这个意义上说,它们让函数的行为有点像具有私有属性的对象。

完整的帖子:

那么这些闭合是什么呢?


闭包是简单:

下面的简单示例涵盖了JavaScript闭包的所有要点。*,

这是一家生产计算器的工厂,可以进行加法和乘法运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

关键点:每次调用make_calculator都会创建一个新的本地变量n,在make_calculator返回后很长时间内,该计算器的addmultiply函数仍然可以使用该变量。

如果您熟悉堆栈帧,那么这些计算器看起来很奇怪:它们如何在make_calculator返回之后继续访问n ?答案是,假设JavaScript不使用"堆栈帧",而是使用"堆帧",在使它们返回的函数调用之后,"堆帧"可以保持。

addmultiply这样的内部函数访问在外部函数**中声明的变量,称为闭包。

这就是闭包的全部内容。

例如,它涵盖了在另一个答案中给出的"闭包For Dummies"文章中的所有要点,除了例6,它只是说明变量可以在声明之前使用,这是一个很好的事实,但与闭包完全无关。它还涵盖了可接受答案中的所有点,除了(1)函数将它们的参数复制到局部变量(命名函数参数)和(2)复制数字会创建一个新数字,但是复制对象引用会给您另一个对相同对象的引用。知道这些也很好,但同样与闭包完全无关。它也非常类似于这个答案中的例子,但是更短,更抽象。它不包括这个答案或评论,那就是JavaScript很难循环变量的当前值插入你的内部函数:"插入"步骤只能与一个helper函数,包含你的内部函数和调用每个循环迭代。(严格地说,内部函数访问helper函数的变量副本,而不是插入任何东西)。同样,在创建闭包时非常有用,但不是闭包是什么或它如何工作的一部分。有额外的混乱是由于关闭工作不同的函数式语言像毫升,在变量绑定值而不是存储空间,提供源源不断的人理解闭包的方式(即"插入"方式),只是错误的JavaScript变量总是绑定到存储空间,永远的价值观。< /一口>

**任何外部函数,如果有几个是嵌套的,甚至在全局上下文中,就像这个答案清楚指出的那样


我是如何向一个六岁的孩子解释的:

你知道成年人怎么能拥有一所房子,他们把它叫做家?当一个母亲有了孩子,孩子实际上并不拥有任何东西,对吗?但是它的父母有房子,所以每当有人问孩子"你家在哪里?",他/她可以回答"那所房子!",指着它父母的房子。"封闭性"指的是孩子总能(即使是在国外)说自己有家,即使房子实际上是父母的。


你能向一个5岁的孩子解释闭包吗

我仍然认为谷歌的解释很好,很简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/


function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);?

Proof that this example creates a closure even if the inner function doesn't return

<子> *一个c#的问题< /子>


我倾向于通过好的/坏的比较学得更好。我喜欢看到工作代码后面跟着一些可能会遇到的非工作代码。我做了一个比较,并试图将差异归结为我能想到的最简单的解释。

闭包做好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}

在上面的代码中,在循环的每次迭代中调用createClosure(n)。注意,我将变量命名为n是为了强调它是在新函数作用域中创建的新变量,与绑定到外部作用域的index不同。

这将创建一个新的范围,n被绑定到该范围;这意味着我们有10个单独的范围,每个迭代一个。

createClosure(n)返回一个函数,该函数返回该范围内的n。

在每个范围内,n被绑定到调用createClosure(n)时它所拥有的任何值,因此返回的嵌套函数将始终返回调用createClosure(n)时它所拥有的n的值。

闭包做错了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}

在上面的代码中,循环在createClosureArray()函数中移动,现在函数只返回完成的数组,乍一看似乎更直观。

可能不太明显的是,由于createClosureArray()只被调用一次,因此只为这个函数创建一个作用域,而不是为循环的每个迭代创建一个作用域。

在这个函数中定义了一个名为index的变量。循环运行并向返回index的数组添加函数。注意,index是在createClosureArray函数中定义的,该函数只被调用一次。

因为在createClosureArray()函数中只有一个作用域,所以index只绑定到该作用域中的一个值。换句话说,每次循环更改index的值时,它都会对该范围内引用它的所有对象进行更改。

添加到数组中的所有函数都从定义它的父范围返回相同的index变量,而不是像第一个示例那样从10个不同的范围返回10个不同的变量。最终的结果是,所有10个函数从相同的作用域返回相同的变量。

在循环完成和index被修改完成后,结束值为10,因此,添加到数组中的每个函数返回单个index变量的值,该变量现在被设置为10。

结果

CLOSURES DONE RIGHT
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

CLOSURES DONE WRONG
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


维基百科在闭包:

In computer science, a closure is a function together with a referencing environment for the nonlocal names (free variables) of that function.

从技术上讲,在JavaScript中,每个函数都是一个闭包。它总是可以访问在周围范围中定义的变量。

由于JavaScript中的作用域定义构造是一个函数,而不是像许多其他语言中的代码块,所以JavaScript中的闭包通常是一个函数,它处理已经执行的周围函数中定义的非本地变量。

闭包通常用于创建具有一些隐藏的私有数据的函数(但并不总是这样)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

上面的例子使用了一个匿名函数,它只执行一次。但这并不是必须的。它可以被命名(例如mkdb),然后执行,每次调用时生成一个数据库函数。每个生成的函数都有自己的隐藏数据库对象。闭包的另一个使用示例是,当我们不返回一个函数,而是返回一个对象,其中包含用于不同目的的多个函数,每个函数都可以访问相同的数据。


我编写了一个交互式JavaScript教程来解释闭包是如何工作的。闭包是什么?

这里有一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

The children will always remember the secrets they have shared with their parents, even after their parents are
gone. This is what closures are for functions.

JavaScript函数的秘密是私有变量

1
2
3
var parent = function() {
 var name ="Mary"; // secret
}

每次调用它时,都会创建局部变量"name"并将其命名为"Mary"。每次函数退出时,变量就会丢失,名字也会忘记。

正如您可能猜到的,因为每次调用函数时都会重新创建变量,而且没有其他人知道它们,所以必须有一个存储这些变量的秘密位置。它可以被称为密室或堆栈或局部范围,但这并不重要。我们知道它们就在那里,某个地方,藏在记忆里。

但JavaScript中有一个很特别的东西,函数是在其它函数中创建的,它还能知道父函数的局部变量,并能一直保存。

1
2
3
4
5
6
var parent = function() {
  var name ="Mary";
  var child = function(childName) {
    // I can also see that"name" is"Mary"
  }
}

因此,只要我们在父函数中,它就可以创建一个或多个子函数,这些子函数共享来自秘密位置的秘密变量。

但不幸的是,如果子函数也是父函数的私有变量,那么父函数结束时,子函数也会终止,而秘密也会随之终止。

所以为了活下去,孩子必须在一切都太迟之前离开

1
2
3
4
5
6
7
8
var parent = function() {
  var name ="Mary";
  var child = function(childName) {
    return"My name is" + childName  +", child of" + name;
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside

现在,即使玛丽"不再跑步",对她的记忆并没有消失,她的孩子将永远记住她的名字和他们在一起时分享的其他秘密。

所以,如果你叫这个孩子"爱丽丝",她会回应

1
child("Alice") =>"My name is Alice, child of Mary"

这就是我要说的。


我不明白为什么这里的答案这么复杂。

以下是结束语:

1
2
3
var a = 42;

function b() { return a; }

是的。你可能一天用很多次。

There is no reason to believe closures are a complex design hack to address specific problems. No, closures are just about using a variable that comes from a higher scope from the perspective of where the function was declared (not run).

Now what it allows you to do can be more spectacular, see other answers.


dlaliberte关于第一点的例子:

A closure is not only created when you return an inner function. In fact, the enclosing function does not need to return at all. You might instead assign your inner function to a variable in an outer scope, or pass it as an argument to another function where it could be used immediately. Therefore, the closure of the enclosing function probably already exists at the time that enclosing function was called since any inner function has access to it as soon as it is called.

1
2
3
4
5
6
7
8
9
var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);


闭包是内部函数访问外部函数中的变量的地方。这可能是对闭包最简单的一行解释。


我知道已经有很多解决方案了,但是我想这个小而简单的脚本可以用来演示这个概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
// makeSequencer will return a"sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

你要过来睡一觉,然后邀请丹。你叫丹带一个XBox控制器来。

丹邀请保罗。丹让保罗带一个控制器来。有多少控制器被带到这个聚会上?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited.
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were" + danInvitedPaul +" controllers brought to the party.");

闭包的作者很好地解释了闭包,解释了我们需要闭包的原因,还解释了理解闭包所必需的词汇环境。
以下是总结:

如果一个变量被访问,但它不是本地的,该怎么办?像在这里:

Enter image description here

在本例中,解释器在外LexicalEnvironment对象。

该过程包括两个步骤:

首先,当函数f被创建时,它不是在空中创建的空间。有一个当前LexicalEnvironment对象。在的情况下上面,它是窗口(a在函数运行时未定义)创建)。

Enter image description here

当一个函数被创建时,它会得到一个名为[[Scope]]的隐藏属性,该属性引用当前的LexicalEnvironment。

Enter image description here

如果读取了一个变量,但是在任何地方都找不到,就会生成一个错误。

嵌套函数

函数可以彼此嵌套在一起,形成一个词汇环境链,也可以称为作用域链。

Enter image description here

函数g可以访问g a和f。

闭包

嵌套函数可以在外部函数完成后继续存在:

Enter image description here

标记LexicalEnvironments:

Enter image description here

正如我们所看到的,this.say是user对象中的一个属性,因此在用户完成之后,它将继续存在。

如果您还记得,在创建this.say时,它(作为每个函数)将获得一个到当前LexicalEnvironment的内部引用this.say.[[Scope]]。因此,当前用户执行的LexicalEnvironment保存在内存中。User的所有变量也都是它的属性,因此它们也被小心地保存,而不是像通常那样被丢弃。

关键是要确保,如果内部函数希望在将来访问外部变量,它能够这样做。

总结:

内部函数保持对外部函数的引用LexicalEnvironment。内部函数可以从中访问变量任何时候,即使外部函数已经完成。浏览器将LexicalEnvironment及其所有属性(变量)保存在内存中,直到有一个内部函数引用它。

这叫做闭包。


JavaScript函数可以访问它们的:

参数局部变量(即局部变量和局部函数)环境,这包括:全局变量,包括DOM任何外部函数

如果一个函数访问它的环境,那么这个函数就是一个闭包。

注意,外部功能不是必需的,尽管它们确实提供了我在这里没有讨论的好处。通过访问其环境中的数据,闭包使该数据保持活动状态。在外部/内部函数的子例中,外部函数可以创建本地数据并最终退出,但是,如果任何内部函数在外部函数退出后仍然存在,那么内部函数将保留外部函数的本地数据。

使用全局环境的闭包示例:

假设堆栈溢出voteUp_click和voteDown_click将voteup和votedown按钮事件实现为闭包,它们可以访问全局定义的外部变量isVotedUp和isVotedDown。(为了简单起见,我指的是StackOverflow的问题投票按钮,而不是答案投票按钮数组。)

当用户单击VoteUp按钮时,voteUp_click函数将检查isVotedDown == true,以确定是投赞成票还是仅仅取消反对票。函数voteUp_click是一个闭包,因为它正在访问它的环境。

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
var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

这四个函数都是闭包,因为它们都访问它们的环境。


作为一个6岁孩子的父亲,我目前正在教孩子们编程(相对来说,他还没有接受过正规教育,因此需要纠正),我认为,通过亲身体验游戏,这门课最容易坚持下去。如果6岁的孩子已经准备好理解什么是结束,那么他们就足够大了,可以自己尝试一下。我建议将代码粘贴到jsfiddle.net中,解释一下,让他们自己编一首独特的歌。下面的解释文字可能更适合10岁的孩子。

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
function sing(person) {

    var firstPart ="There was" + person +" who swallowed";

    var fly = function() {
        var creature ="a fly";
        var result ="Perhaps she'll die";
        alert(firstPart + creature +"
"
+ result);
    };

    var spider = function() {
        var creature ="a spider";
        var result ="that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature +"
"
+ result);
    };

    var bird = function() {
        var creature ="a bird";
        var result ="How absurd!";
        alert(firstPart + creature +"
"
+ result);
    };

    var cat = function() {
        var creature ="a cat";
        var result ="Imagine That!";
        alert(firstPart + creature +"
"
+ result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

指令

数据:数据是事实的集合。它可以是数字,文字,测量,观察,甚至只是对事物的描述。你不能摸它,闻它,尝它。你可以把它写下来,说出来,听出来。你可以用它来创造触摸,嗅觉和味觉使用电脑。计算机使用代码可以使它变得有用。

代码:上面所有的代码都称为代码。它是用JavaScript编写的。

JAVASCRIPT: JAVASCRIPT是一种语言。像英语、法语或汉语都是语言。计算机和其他电子处理器可以理解许多语言。要使计算机能够理解JavaScript,它需要一个解释器。想象一下,如果一个只会说俄语的老师来学校给你们上课。当老师说"всесадятся"类不会理解。但幸运的是,你们班上有一个俄罗斯学生,他告诉每个人这句话的意思是"每个人都坐下"——所以你们都坐下了。这个班就像一台电脑,那个俄罗斯学生就是翻译。对于JavaScript,最常见的解释器称为浏览器。

浏览器:当你用电脑、平板电脑或手机上网访问一个网站时,你使用的是浏览器。你可能知道的例子有Internet Explorer, Chrome, Firefox和Safari。浏览器可以理解JavaScript并告诉计算机它需要做什么。JavaScript指令称为函数。

函数:JavaScript中的函数就像一个工厂。它可能是一个只有一台机器的小工厂。或者它可能包含许多其他的小工厂,每个小工厂都有许多机器做不同的工作。在现实生活中的服装厂,你可能会有大量的布料和线轴穿进去,t恤和牛仔裤穿出来。我们的JavaScript工厂只处理数据,它不能缝,钻洞或熔化金属。在JavaScript工厂中,数据输入和输出。

所有这些数据听起来有点无聊,但它真的很酷;我们可能有一个功能告诉机器人晚餐做什么。假设我邀请你和你的朋友来我家。你最喜欢鸡腿,我喜欢香肠,你的朋友总是想吃你想要的,而我的朋友不吃肉。

我没有时间去购物,所以函数需要知道冰箱里有什么来做决定。每一种食材都有不同的烹饪时间,我们希望所有食物都由机器人同时热端上桌。我们需要给这个功能提供我们喜欢的数据,这个功能可以和冰箱"交谈",这个功能可以控制机器人。

函数通常有名称、圆括号和大括号。是这样的:

1
function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

注意,/*...*///停止浏览器读取代码。

NAME:您可以调用一个函数,无论您想调用哪个单词。"cookMeal"这个例子很典型,它将两个单词连在一起,并在开头用大写字母表示第二个单词——但这是不必要的。它不能有空格,也不能是一个单独的数字。

圆括号:"圆括号"或()是JavaScript函数工厂门口的信箱或街道上的邮筒,用于向工厂发送信息包。有时,postbox可能会被标记为cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime),在这种情况下,您知道必须给它什么数据。

大括号:"大括号"看起来像这样的{}是我们工厂的有色窗口。从工厂里你可以看到外面,但从外面你看不到里面。

上面的长代码示例

我们的代码以单词function开头,所以我们知道它是1 !然后是函数的名字,这是我对函数的描述。然后括号()。函数的括号总是在那里。有时它们是空的,有时它们里面有东西。这个函数中有一个词:(person)。在这之后有一个像这样的大括号{。这标志着函数sing()的开始。它有一个伙伴标志着sing()的结束,如下所示}

1
function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

所以这个函数可能和唱歌有关,可能需要一些关于一个人的数据。它内部有处理这些数据的指令。

现在,在函数sing()之后,在代码的最后一行

1
var person="an old lady";

变量:var代表"变量"。变量就像一个信封。信封的外面写着"人"。在它的内部包含一张纸条,上面写着我们的功能需要的信息,一些字母和空格像一根绳子一样连接在一起(它被称为绳子),组成了一个短语"一个老太太"。我们的信封可以包含其他类型的东西,比如数字(称为整数)、指令(称为函数)、列表(称为数组)。因为这个变量是在所有大括号{}之外编写的,而且当您在大括号内时可以通过着色的窗口看到外面,所以可以从代码中的任何位置看到这个变量。我们称之为"全局变量"。

全局变量:人是一个全局变量,也就是说,如果你改变它的值从一个"老妇人"到"年轻人",这个人将继续作为一个年轻人,直到你决定换一遍,任何其他函数代码中可以看到,这是一个年轻人。按下F12按钮,或者查看选项设置,打开浏览器的开发人员控制台并键入"person",查看这个值是什么。键入person="a young man"来更改它,然后再次键入"person"以查看它是否已更改。

然后是直线

1
sing(person);

这一行调用函数,就像调用一条狗一样

"Come on sing, Come and get person!"

当浏览器加载JavaScript代码并到达这一行时,它将启动函数。我把这一行放在最后,以确保浏览器拥有运行它所需的所有信息。

功能定义动作——主要功能是唱歌。它包含了一个叫做firstPart的变量,这个变量适用于歌曲中每一段歌词中所唱的那个人:"There was + person + who"。如果你在控制台中输入firstPart,你不会得到一个答案,因为变量被锁定在一个函数中——浏览器不能在带颜色的大括号窗口中看到。

闭包:闭包是big sing()函数中较小的函数。大工厂里的小工厂。它们都有自己的大括号,这意味着它们内部的变量不能从外部看到。这就是为什么变量的名称(生物和结果)可以在闭包中重复,但是值不同。如果在控制台窗口中键入这些变量名,就不会得到它的值,因为它被两层着色的窗口所隐藏。

闭包都知道sing()函数的变量firstPart是什么,因为它们可以从着色的窗口看到外面。

在闭包之后是行

1
2
3
4
fly();
spider();
bird();
cat();

函数的作用是:按照给定的顺序调用这些函数。然后,将完成sing()函数的工作。


好吧,和一个6岁的孩子交谈,我可能会用下面的联想。

Imagine - you are playing with your little brothers and sisters in the entire house, and you are moving around with your toys and brought some of them into your older brother's room. After a while your brother returned from the school and went to his room, and he locked inside it, so now you could not access toys left there anymore in a direct way. But you could knock the door and ask your brother for that toys. This is called toy's closure; your brother made it up for you, and he is now into outer scope.

与汇票的情况一扇门是锁着的,没有人在(通用函数执行),然后一些地方发生火灾,烧毁的房间(垃圾收集器:D),然后一个新的房间是构建和现在你可能离开另一个玩具(新功能),但从未得到相同的玩具离开房间在第一个实例。

对于一个高智商的孩子,我想说如下的话。它并不完美,但它让你感觉到它是什么:

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
42
function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy &amp;&amp; closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

正如你所看到的,留在房间里的玩具仍然可以通过哥哥得到,无论房间是否锁上了。这里有一个jsbin来处理它。


一个六岁孩子的答案(假设他知道函数是什么,变量是什么,数据是什么):

函数可以返回数据。可以从函数返回的一种数据是另一种函数。当新函数返回时,创建它的函数中使用的所有变量和参数都不会消失。相反,父函数"关闭"。换句话说,除了返回的函数之外,没有任何东西可以查看它的内部并查看它使用的变量。这个新函数具有一种特殊的功能,可以回头查看创建它的函数内部并查看其中的数据。

1
2
3
4
5
6
7
8
9
function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

另一个非常简单的解释方法是范围:

任何时候,当您在较大的范围内创建较小的范围时,较小的范围总是能够看到较大范围内的内容。


JavaScript中的函数不仅是对一组指令的引用(与C语言中一样),而且还包含一个隐藏的数据结构,该结构由对它使用的所有非本地变量(捕获的变量)的引用组成。这样的两部分函数称为闭包。JavaScript中的每个函数都可以看作一个闭包。

闭包是带有状态的函数。它有点类似于"this",因为"this"也为函数提供状态,但是函数和"this"是独立的对象("this"只是一个花哨的参数,将它永久绑定到函数的唯一方法是创建一个闭包)。虽然"this"和函数总是独立存在的,但是函数不能与其闭包分离,并且该语言不提供访问捕获变量的方法。

因为所有这些外部变量引用的一个词法嵌套函数实际上是链中的局部变量的词法封闭功能(全局变量可以被认为是一些根函数的局部变量),和每一个执行的函数创建新实例的本地变量,因此,每执行一个函数返回(或转移出来,例如将它注册为回调函数)嵌套函数创建一个新的闭包(使用它自己潜在的惟一的被引用的非本地变量集来表示它的执行上下文)。

此外,必须理解JavaScript中的局部变量不是在堆栈帧上创建的,而是在堆上创建的,只有在没有人引用它们时才销毁它们。当函数返回时,引用其本地变量是递减的,但他们仍然可以非空,如果在当前执行他们成为一个闭包的一部分,仍由其引用词法嵌套函数(仅可能发生如果引用这些嵌套函数返回或者转移到一些外部代码)。

一个例子:

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
function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

也许除了最早熟的6岁孩子之外,其他孩子对闭包的理解都有点超出了,但是有几个例子帮助我理解了JavaScript中的闭包概念。

闭包是一个可以访问另一个函数的作用域(它的变量和函数)的函数。创建闭包最简单的方法是在函数中使用函数;原因是在JavaScript中,函数总是可以访问其包含的函数的作用域。

1
2
3
4
5
6
7
8
9
10
11
function outerFunction() {
    var outerVar ="monkey";
   
    function innerFunction() {
        alert(outerVar);
    }
   
    innerFunction();
}

outerFunction();

警告:猴子

在上面的例子中,调用outer函数,然后调用innerFunction。注意outerVar是如何对innerFunction可用的,它正确地警告outerVar的值就证明了这一点。

现在考虑以下问题:

1
2
3
4
5
6
7
8
9
10
11
12
function outerFunction() {
    var outerVar ="monkey";
   
    function innerFunction() {
        return outerVar;
    }
   
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

警告:猴子

referenceToInnerFunction被设置为outerFunction(),它只返回对innerFunction的引用。当调用referenceToInnerFunction时,它返回outerVar。同样,如上所述,这表明innerFunction可以访问outerVar,这是outerFunction的一个变量。此外,值得注意的是,即使在outerFunction完成执行之后,它仍然保留这种访问。

这就是事情变得非常有趣的地方。如果我们要去掉outerFunction,比如把它设为null,你可能会认为referenceToInnerFunction会失去对outerVar值的访问权。但事实并非如此。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function outerFunction() {
    var outerVar ="monkey";
   
    function innerFunction() {
        return outerVar;
    }
   
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

警告:猴子警告:猴子

但这是怎么回事呢?既然outerFunction已经被设置为null, referenceToInnerFunction怎么还能知道outerVar的值呢?

referenceToInnerFunction仍然可以访问outerVar的值的原因是,当第一次创建闭包时,innerFunction将innerFunction放在outerFunction的内部,然后将对outerFunction作用域(变量和函数)的引用添加到作用域链中。这意味着innerFunction有一个指向outerFunction所有变量的指针或引用,包括outerVar。因此,即使outerFunction已经执行完毕,或者即使它被删除或设置为null,它作用域中的变量,比如outerVar,仍然保留在内存中,因为返回到referenceToInnerFunction的innerFunction部分对它们有出色的引用。要真正从内存中释放outerVar和outerFunction的其他变量,您必须去掉对它们的这个突出引用,比如将referenceToInnerFunction设置为null。

//////////

关于闭包还有两件事需要注意。首先,闭包总是可以访问它所包含函数的最后一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
function outerFunction() {
    var outerVar ="monkey";
   
    function innerFunction() {
        alert(outerVar);
    }
   
    outerVar ="gorilla";

    innerFunction();
}

outerFunction();

警告:大猩猩

其次,当创建闭包时,它保留对其所有封闭函数的变量和函数的引用;它没有选择的权利。但是,闭包应该谨慎使用,或者至少谨慎使用,因为它们可能占用内存;在包含的函数执行完毕后,许多变量可以在内存中保存很长时间。


我只需将它们指向Mozilla closure页面。这是我发现的对闭包基础知识和实际用法的最好、最简洁和简单的解释。强烈推荐任何学习JavaScript的人。

是的,我甚至向一个6岁的孩子推荐它——如果这个6岁的孩子正在学习闭包,那么他们准备好理解本文中提供的简洁而简单的解释是合乎逻辑的。


我相信简短的解释,所以请看下图。

Enter image description here

function f1() . .亮红色框

function f2() . .>红色小盒子

这里我们有两个函数,f1()f2()。f2()是f1()的内部。f1()有一个变量var x = 10

调用函数f1()时,f2()可以访问var x = 10的值。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
function f1() {
    var x=10;

    function f2() {
        console.log(x)
    }

    return f2

}
f1()

f1()调用:

Enter image description here


A closure is a function having access to the parent scope, even after the parent function has closed.

所以闭包基本上是另一个函数的函数。我们可以说像一个子函数。

A closure is an inner function that has access to the outer
(enclosing) function’s variables—scope chain. The closure has three
scope chains: it has access to its own scope (variables defined
between its curly brackets), it has access to the outer function’s
variables, and it has access to the global variables.

The inner function has access not only to the outer function’s
variables but also to the outer function’s parameters. Note that the
inner function cannot call the outer function’s arguments object,
however, even though it can call the outer function’s parameters
directly.

You create a closure by adding a function inside another function.

同时,它也是一个非常有用的方法,在许多著名的框架中使用,包括AngularNode.jsjQuery:

Closures are used extensively in Node.js; they are workhorses in
Node.js’ asynchronous, non-blocking architecture. Closures are also
frequently used in jQuery and just about every piece of JavaScript
code you read.

但是闭包在实际编码中是什么样子的呢?看看这个简单的示例代码:

1
2
3
4
5
6
7
8
9
10
function showName(firstName, lastName) {
      var nameIntro ="Your name is";
      // this inner function has access to the outer function's variables, including the parameter
      function makeFullName() {
          return nameIntro + firstName +"" + lastName;
      }
      return makeFullName();
  }

  console.log(showName("Michael","Jackson")); // Your name is Michael Jackson

此外,这是jQuery中的经典闭包方法,每个javascript和jQuery开发人员都经常使用它:

1
2
3
4
5
6
$(function() {
    var selections = [];
    $(".niners").click(function() { // this closure has access to the selections variable
        selections.push(this.prop("name")); // update the selections variable in the outer function's scope
    });
});

但是为什么要使用闭包呢?当我们在实际编程中使用它时?闭包的实际用途是什么?下面是MDN的一个很好的解释和例子:

实际的闭包

Closures are useful because they let you associate some data (the
lexical environment) with a function that operates on that data. This
has obvious parallels to object oriented programming, where objects
allow us to associate some data (the object's properties) with one or
more methods.

Consequently, you can use a closure anywhere that you might normally
use an object with only a single method.

Situations where you might want to do this are particularly common on
the web. Much of the code we write in front-end JavaScript is
event-based — we define some behavior, then attach it to an event that
is triggered by the user (such as a click or a keypress). Our code is
generally attached as a callback: a single function which is executed
in response to the event.

For instance, suppose we wish to add some buttons to a page that
adjust the text size. One way of doing this is to specify the
font-size of the body element in pixels, then set the size of the
other elements on the page (such as headers) using the relative em
unit:

请阅读下面的代码并运行代码,看看闭包如何帮助我们在这里轻松地为每个部分创建单独的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//javascript
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
1
2
3
4
5
6
7
8
9
10
11
12
13
/*css*/
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}
1
2
3
4
5
6
7
8
9
10
<!--html><!-->
<p>
Some paragraph text
</p>
<hh1>some heading 1 text</hh1>
<hh2>some heading 2 text</hh2>

12
14
16

关于闭包的进一步研究,我建议您通过MDN访问这个页面:https://developer.mozilla.org/en/docs/Web/JavaScript/Closures


一个六岁吗?

你和你的家人住在神话般的小镇安维尔。你有一个住在隔壁的朋友,所以你打电话叫他们出来玩。你拨打:

000001 (jamiesHouse)

一个月后,你和你的家人从安威尔搬到了下一个城镇,但你和你的朋友仍然保持联系,所以现在你必须先拨你朋友所在城镇的区号,然后再拨他们的"正确"号码:

001 000001 (annVille.jamiesHouse)

一年后,你的父母搬到了一个全新的国家,但你和你的朋友仍然保持联系,所以在烦你的父母让你打国际长途之后,你现在拨:

01 001 000001 (myOldCountry.annVille.jamiesHouse)

奇怪的是,在搬到你的新国家后,你和你的家人恰好搬到了一个叫安维尔的小镇上。你正好和一个叫杰米的新朋友交了朋友……你给他们打个电话……

000001 (jamiesHouse)

幽灵……

你竟然把这件事告诉了你老家的杰米……你笑得真开心。所以,有一天,你和你的家人去旧国家度假。你参观了你的老城(安维尔),去拜访杰米……

"真的吗?另一个杰米?在安城镇吗?在你的新国家!!?""是啊……我们叫他们……"

02 001 000001 (myNewCountry.annVille.jamiesHouse)

意见吗?

更重要的是,我有一大堆关于现代六岁孩子耐心的问题……


在JavaScript中,闭包非常棒,内部函数可以使用变量或参数,即使在外部函数返回之后,闭包仍然是活动的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  function getFullName(a, b) {
  return a + b;
}

function makeFullName(fn) {

  return function(firstName) {

    return function(secondName) {

      return fn(firstName, secondName);
    }
  }
}

makeFullName(getFullName)("stack")("overflow"); // Stackoverflow


下面是一个简单的实时场景。只要通读一遍,你就会明白我们这里是如何使用闭包的(参见座位号的变化)。

前面解释的所有其他例子也非常有助于理解这个概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function movieBooking(movieName) {
    var bookedSeatCount = 0;
    return function(name) {
        ++bookedSeatCount ;
        alert( name +" -" + movieName +", Seat -" + bookedSeatCount )
    };
};

var MI1 = movieBooking("Mission Impossible 1");
var MI2 = movieBooking("Mission Impossible 2");

MI1("Mayur");
// alert
// Mayur - Mission Impossible 1, Seat - 1

MI1("Raju");
// alert
// Raju - Mission Impossible 1, Seat - 2

MI2("Priyanka");
// alert
// Raja - Mission Impossible 2, Seat - 1

闭包允许JavaScript程序员编写更好的代码。创造性,表达力强,简洁。我们经常在JavaScript中使用闭包,而且,无论我们的JavaScript经验如何,毫无疑问,我们会一次又一次地遇到闭包。闭包可能看起来很复杂,但希望在您阅读本文之后,闭包会更容易理解,从而更适合您的日常JavaScript编程任务。

在进一步阅读之前,您应该熟悉JavaScript变量范围,因为要理解闭包,您必须理解JavaScript的变量范围。

什么是闭包?

闭包是一个内部函数,它可以访问外部(封闭的)函数的变量范围链。闭包有三个作用域链:它可以访问自己的作用域(在它的花括号中定义的变量),它可以访问外部函数的变量,它还可以访问全局变量。

内部函数不仅可以访问外部函数的变量,还可以访问外部函数的参数。注意,内部函数不能调用外部函数的arguments对象,尽管它可以直接调用外部函数的参数。

通过在另一个函数中添加一个函数来创建闭包。

JavaScript中闭包的一个基本示例:?

1
2
3
4
5
6
7
8
9
10
11
function showName (firstName, lastName) {?
  var nameIntro ="Your name is";
  // this inner function has access to the outer function's variables, including the parameter
  ?function makeFullName () {?            
?    return nameIntro + firstName +"" + lastName;?        
  }
?
?  return makeFullName ();?
}?
?
showName ("Michael","Jackson"); // Your name is Michael Jackson?

闭包在Node.js中被广泛使用;他们是Node的主力。js的异步、非阻塞架构。闭包在jQuery和您阅读的几乎所有JavaScript代码中也经常使用。

一个典型的jQuery闭包示例:?

1
2
3
4
5
6
7
$(function() {
?
?  var selections = [];
  $(".niners").click(function() { // this closure has access to the selections variable?
    selections.push (this.prop("name")); // update the selections variable in the outer function's scope?
  });
?});

闭包的规则和副作用

1. 闭包可以访问外部函数的变量,即使外部函数返回:

闭包最重要和最棘手的特性之一是,即使在外部函数返回之后,内部函数仍然可以访问外部函数的变量。是的,你没看错。当JavaScript中的函数执行时,它们使用与创建时相同的作用域链。这意味着即使在外部函数返回之后,内部函数仍然可以访问外部函数的变量。因此,您可以在稍后的程序中调用内部函数。这个案例展示了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function celebrityName (firstName) {
    var nameIntro ="This celebrity is";
    // this inner function has access to the outer function's variables, including the parameter?
   function lastName (theLastName) {
        return nameIntro + firstName +"" + theLastName;
    }
    return lastName;
}
?
?var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.?
?
?// The closure (lastName) is called here after the outer function has returned above?
?// Yet, the closure still has access to the outer function's variables and parameter?
mjName ("Jackson"); // This celebrity is Michael Jackson?

2. 闭包存储对外部函数变量的引用:

它们不存储实际值。当外部函数变量的值在调用闭包之前发生变化时,闭包会变得更有趣。而这一强大的功能可以通过创造性的方式加以利用,比如道格拉斯·克罗克福德(Douglas Crockford)首次展示的私有变量示例:?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function celebrityID () {
    var celebrityID = 999;
    // We are returning an object with some inner functions?
    // All the inner functions have access to the outer function's variables?
    return {
        getID: function ()  {
            // This inner function will return the UPDATED celebrityID variable?
            // It will return the current value of celebrityID, even after the changeTheID function changes it?
          return celebrityID;
        },
        setID: function (theNewID)  {
            // This inner function will change the outer function's variable anytime?
            celebrityID = theNewID;
        }
    }
?
}
?
?var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.?
mjID.getID(); // 999?
mjID.setID(567); // Changes the outer function's variable?
mjID.getID(); // 567: It returns the updated celebrityId variable?

3.闭包失败

因为闭包可以访问外部函数变量的更新值,所以当外部函数的变量使用for循环进行更改时,闭包也会导致bug。因此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// This example is explained in detail below (just after this code box).?
?function celebrityIDCreator (theCelebrities) {
    var i;
    var uniqueID = 100;
    for (i = 0; i < theCelebrities.length; i++) {
      theCelebrities[i]["id"] = function ()  {
        return uniqueID + i;
      }
    }
   
    return theCelebrities;
}
?
?var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];
?
?var createIdForActionCelebs = celebrityIDCreator (actionCelebs);
?
?var stalloneID = createIdForActionCelebs [0];??    console.log(stalloneID.id()); // 103

可以在这里找到更多-

http://javascript.info/tutorial/closures

http://www.javascriptkit.com/javatutors/closures.shtml


以下是我能给出的最具禅意的答案:

您希望这段代码做什么?运行前请在评论中告诉我。我很好奇!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {
  var i = 1;
  return function() {
    console.log(i++);
  }
}

var bar = foo();
bar();
bar();
bar();

var baz = foo();
baz();
baz();
baz();

现在打开浏览器中的控制台(Ctrl + Shift + IF12,希望如此),粘贴代码并按Enter

如果这段代码输出了您所期望的内容(JavaScript新手-忽略末尾的"undefined"),那么您已经有了无言的理解。换句话说,变量i是内部函数实例闭包的一部分。

我这样做是因为,一旦我理解了这段代码将foo()的内部函数实例放在barbaz中,然后通过这些变量调用它们,我就不会感到惊讶了。

但如果我错了,控制台输出让您感到惊讶,请告诉我!


我越想闭包,就越觉得它是一个分两步的过程:init - action

1
2
init: pass first what's needed...
action: in order to achieve something for later execution.

对于一个6岁的孩子,我想强调的是结束的实际方面:

1
2
3
4
5
6
Daddy: Listen. Could you bring mum some milk (2).
Tom: No problem.
Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.
Daddy: But get ready first. And bring the map with you (1), it may come in handy
Daddy: Then off you go (3). Ok?
Tom: A piece of cake!

给妈妈带些牛奶来。首先准备好地图(=init)。

1
2
3
4
5
6
7
8
function getReady(map) {
    var cleverBoy = 'I examine the ' + map;
    return function(what, who) {
        return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map
    }
}
var offYouGo = getReady('daddy-map');
offYouGo('milk', 'mum');

因为如果你带着一条非常重要的信息(地图),你就有足够的知识来执行其他类似的操作:

1
offYouGo('potatoes', 'great mum');

对于开发人员,我将把闭包和OOP进行比较。init阶段类似于将参数传递给传统OO语言中的构造函数;行动阶段最终是您为实现您想要的而调用的方法。这个方法可以使用一个叫做闭包的机制来访问这些init参数。

参见我的另一个解释OO和闭包并行性的答案:

如何在JavaScript中"正确"创建自定义对象?


给出如下函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function person(name, age){

    var name = name;
    var age = age;

    function introduce(){
        alert("My name is"+name+", and I'm"+age);
    }

    return introduce;
}

var a = person("Jack",12);
var b = person("Matt",14);

每次调用函数person时,都会创建一个新的闭包。虽然变量ab具有相同的introduce函数,但是它链接到不同的闭包。即使在函数person执行完成之后,该闭包仍然存在。

Enter image description here

1
2
a(); //My name is Jack, and I'm 12
b(); //My name is Matt, and I'm 14

抽象闭包可以表示为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
closure a = {
    name:"Jack",
    age: 12,
    call: function introduce(){
        alert("My name is"+name+", and I'm"+age);
    }
}

closure b = {
    name:"Matt",
    age: 14,
    call: function introduce(){
        alert("My name is"+name+", and I'm"+age);
    }
}

假设您知道class在另一种语言中是如何工作的,我将做一个类比。

JavaScript function作为一个constructorlocal variables, instance properties这些properties是私有的inner functions, instance methods

每次调用function

将创建一个包含所有本地变量的新object。该对象的方法可以访问该实例对象的"properties"


要理解闭包,您必须深入到程序中,并像运行时一样执行闭包。让我们看看这段简单的代码:

Enter image description here

JavaScript分两个阶段运行代码:

编译阶段// JavaScript不是一种纯解释语言执行阶段

当JavaScript经过编译阶段时,它会提取变量和函数的声明。这就是所谓的提升。这个阶段遇到的函数被保存为内存中的文本块,也称为lambda。编译后,JavaScript进入执行阶段,在此阶段它分配所有值并运行函数。为了运行函数,它通过从堆中分配内存并重复函数的编译和执行阶段来准备执行上下文。这个内存区域称为函数的作用域。执行开始时有一个全局范围。范围是理解闭包的关键。

在本例中,首先定义变量a,然后在编译阶段定义f。所有未声明的变量都保存在全局范围中。在执行阶段使用一个参数调用f。指定f的作用域,并为其重复编译和执行阶段。

参数也保存在这个局部范围内的f。无论何时创建本地执行上下文或范围,它都包含指向其父范围的引用指针。所有变量访问都遵循这个词法范围链来查找它的值。如果在局部作用域中没有找到变量,它将遵循该链并在其父作用域中找到它。这也是为什么局部变量覆盖父范围中的变量。父作用域称为局部作用域或函数的"闭包"。

在这里,当g的作用域被设置时,它得到一个词法指针,指向它的父作用域ff的范围是g的闭包。在JavaScript中,如果有一些对函数、对象或范围的引用(如果您能够以某种方式访问它们),那么它就不会被垃圾收集。因此,当myG运行时,它有一个指向f范围的指针,这是它的闭包。即使f已经返回,该内存区域也不会被垃圾回收。就运行时而言,这是一个闭包。

那么什么是闭包?它是函数及其作用域链之间的隐式、永久性链接……函数定义的(lambda)隐藏的[[scope]]引用。保持范围链(防止垃圾收集)。在函数运行时,它被用作"外部环境引用"并被复制。

隐闭包

1
2
3
4
var data ="My Data!";
setTimeout(function() {
  console.log(data); // Prints"My Data!"
}, 3000);

显式关闭

1
2
3
4
5
6
7
8
9
10
function makeAdder(n) {
  var inc = n;
  var sum = 0;
  return function add() {
    sum = sum + inc;
    return sum;
  };
}

var adder3 = makeAdder(3);

关于闭包和更多闭包的一个非常有趣的讨论是Arindam Paul—JavaScript VM内部、EventLoop、Async和ScopeChains。


闭包是函数中的函数,它可以访问"父"函数的变量和参数。

例子:

1
2
3
4
5
6
7
8
9
10
11
function showPostCard(Sender, Receiver) {

    var PostCardMessage =" Happy Spring!!! Love,";

    function PreparePostCard() {
        return"Dear" + Receiver + PostCardMessage + Sender;
    }

    return PreparePostCard();
}
showPostCard("Granny","Olivia");

尽管互联网上有许多漂亮的JavaScript闭包定义,但我正试图开始用我最喜欢的闭包定义来解释我6岁的朋友,这些定义帮助我更好地理解闭包。

什么是闭包?

闭包是一个内部函数,它可以访问外部(封闭的)函数的变量范围链。闭包有三个作用域链:它可以访问自己的作用域(在它的花括号中定义的变量),它可以访问外部函数的变量,它还可以访问全局变量。

闭包是函数的局部变量——在函数返回后保持活动。

闭包是引用独立(自由)变量的函数。换句话说,在闭包中定义的函数"记住"创建它的环境。

闭包是范围概念的扩展。使用闭包,函数可以访问在函数创建的范围内可用的变量。

闭包是一个堆栈帧,当函数返回时它没有被释放。(就好像堆栈帧被malloc'ed了,而不是在堆栈上!)

像Java这样的语言提供了声明私有方法的能力,这意味着它们只能被同一个类中的其他方法调用。JavaScript没有提供实现此目的的本机方法,但是可以使用闭包模拟私有方法。

"闭包"是一个表达式(通常是一个函数),它可以有自由变量和绑定这些变量的环境("关闭"表达式)。

闭包是一种抽象机制,允许您非常干净地分离关注点。

使用闭包:

闭包在隐藏功能实现的同时仍然显示接口时非常有用。

您可以使用闭包模拟JavaScript中的封装概念。

闭包在jQuery和Node.js中被广泛使用。

虽然创建对象文本当然很容易,而且存储数据也很方便,但是对于在大型web应用程序中创建静态单例名称空间,闭包通常是更好的选择。

闭包的例子:

假设我6岁的朋友最近在小学学习加法,我觉得这个把两个数相加的例子对6岁的孩子来说是最简单的,也是最合适的。

例1:这里通过返回一个函数来实现闭包。

1
2
3
4
5
6
7
8
9
10
11
function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

例2:这里通过返回一个对象文本来实现闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
function makeAdder(x) {
    return {
        add: function(y){
            return x + y;
        }
    }
}

var add5 = makeAdder(5);
console.log(add5.add(2));//7

var add10 = makeAdder(10);
console.log(add10.add(2));//12

示例3:jQuery中的闭包

1
2
3
4
5
6
$(function(){
    var name="Closure is easy";
    $('div').click(function(){
        $('p').text(name);
    });
});

有用的链接:

闭包(Mozilla开发人员网络)轻松理解JavaScript闭包

感谢上面的链接,帮助我更好地理解和解释了闭包。


满足以下说明:JavaScript闭包在幕后是如何工作的。

本文解释了如何以一种直观的方式分配和使用范围对象(或LexicalEnvironment)。例如,对于这个简单的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"use strict";

var foo = 1;
var bar = 2;

function myFunc() {
  //-- Define local-to-function variables
  var a = 1;
  var b = 2;
  var foo = 3;
}

//-- And then, call it:
myFunc();

在执行顶层代码时,范围对象的排列如下:

Enter image description here

当调用myFunc()时,我们有以下作用域链:

Enter image description here

了解范围对象是如何创建、使用和删除的,这是了解闭包是如何工作的关键。

有关详细信息,请参阅前面的文章。


(我没有把6岁的孩子考虑在内。)

在JavaScript这样的语言中,可以将函数作为参数传递给其他函数(函数是第一类公民的语言),您经常会发现自己在做以下事情:

1
2
3
4
5
var name = 'Rafael';

var sayName = function() {
  console.log(name);
};

您可以看到,sayName没有name变量的定义,但是它使用了name的值,该值是在sayName之外(父范围内)定义的。

假设您将sayName作为参数传递给另一个函数,该函数将调用sayName作为回调:

1
functionThatTakesACallback(sayName);

注意:

将从functionThatTakesACallback内部调用sayName(假设,因为在本例中我没有实现functionThatTakesACallback)。当调用sayName时,它将记录name变量的值。functionThatTakesACallback没有定义name变量(嗯,它可以定义,但是没有关系,所以假设它没有)。

因此,我们在functionThatTakesACallback中调用sayName,并引用在functionThatTakesACallback中没有定义的name变量。

然后会发生什么呢?ReferenceError: name is not defined ?

不!name的值在闭包中捕获。您可以将此闭包看作与一个函数关联的上下文,该函数包含定义该函数时可用的值。

因此:即使name不在将调用函数sayName的范围内(在functionThatTakesACallback内部),sayName也可以访问在与sayName关联的闭包中捕获的name的值。

--

来自《雄辩的JavaScript》一书:

A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees its original environment, not the environment in which the call is made.


此答案的版本图片:[已解决]

忘记作用域,记住:当某个地方需要一个变量时,javascript不会销毁它。变量总是指向最新的值。

示例1:

enter image description here

示例2:

enter image description here

示例3:enter image description here


下面的示例是JavaScript闭包的一个简单示例。这是闭包函数,它返回一个函数,访问它的局部变量x,

1
2
3
4
5
function outer(x){
     return function inner(y){
         return x+y;
     }
}

像这样调用函数:

1
2
3
4
5
6
7
var add10 = outer(10);
add10(20); // The result will be 30
add10(40); // The result will be 50

var add20 = outer(20);
add20(20); // The result will be 40
add20(40); // The result will be 60

这个答案是对youtube视频Javascript闭包的总结。这段视频的功劳很大。

闭包只是维护私有变量状态的有状态函数。

通常,当您调用如下图所示的函数时。变量是在使用的堆栈(运行RAM内存)上创建的,然后被分配。

enter image description here

但现在,我们希望维护函数的这种状态,Javascript闭包就是在这种情况下使用的。闭包是函数内部的函数,具有一个返回调用,如下面的代码所示。

enter image description here

因此上面计数器函数的闭包代码如下所示。它是一个函数内函数与返回语句。

1
2
3
4
5
6
7
8
9
10
11
function Counter() {
           var counter = 0;

           var Increment = function () {
               counter++;
               alert(counter);
           }
           return {
               Increment
           }
       }

现在,如果你调用计数器,它会增加,换句话说,函数调用会保持状态。

1
2
3
var x = Counter(); // get the reference of the closure
x.Increment(); // Displays 1
x.Increment(); // Display 2 ( Maintains the private variables)

但现在最大的问题是,这种有状态函数有什么用。有状态函数是实现OOP概念(如抽象、封装和创建自包含模块)的构件。

所以无论你想封装什么,你都可以把它作为私有的,公开给公共的东西应该放在in return语句中。此外,这些组件是自包含的孤立对象,因此它们不会污染全局变量。

遵循OOP原则的对象是自包含的、抽象的、封装的,等等。由于Javascript中没有闭包,这很难实现。

enter image description here


闭包是许多JavaScript开发人员一直在使用的东西,但是我们认为它是理所当然的。它的工作原理并不复杂。理解如何有目的地使用它是复杂的。

按照最简单的定义(正如其他答案所指出的),闭包基本上是在另一个函数中定义的函数。内部函数可以访问外部函数范围内定义的变量。使用闭包最常见的实践是在全局范围内定义变量和函数,并在该函数的函数范围内访问这些变量。

1
2
3
4
5
6
7
8
9
10
11
12
var x = 1;
function myFN() {
  alert(x); //1, as opposed to undefined.
}
// Or
function a() {
   var x = 1;
   function b() {
       alert(x); //1, as opposed to undefined.
   }
   b();
}

那又怎样?

对于JavaScript用户来说,闭包并没有什么特别之处,除非您考虑过没有闭包的生活会是什么样子。在其他语言中,函数中使用的变量在函数返回时被清除。在上面的例子中,x是一个"空指针",您需要建立一个getter和setter并开始传递引用。听起来不像JavaScript,对吧?感谢强大的终结。

我为什么要在乎?

要使用闭包,实际上并不需要知道闭包。但正如其他人也指出的,它们可以被用来创建伪私有变量。在需要私有变量之前,像往常一样使用它们。


我找到了非常清晰的第8章第6节,JavaScript的"闭包":David Flanagan的权威指南,第6版,O'Reilly, 2011。我试着解释一下。

当调用一个函数时,将创建一个新对象来保存该调用的本地变量。

函数的作用域取决于它的声明位置,而不是它的执行位置。

现在,假设在外部函数中声明了一个内部函数,并引用该外部函数的变量。进一步假设外部函数作为函数返回内部函数。现在有一个外部引用,指向内部函数作用域中的任何值(根据我们的假设,其中包括来自外部函数的值)。

JavaScript将保留这些值,因为由于从已完成的外部函数传递出去,这些值仍然在当前执行的范围内。所有的函数都是闭包,但是我们所关心的闭包是内部函数,在我们假设的场景中,当它们(内部函数)从外部函数返回时,内部函数会在它们的"外壳"中保留外部函数值(我希望我在这里使用的语言是正确的)。我知道这不能满足6岁孩子的要求,但希望它仍然有用。


来自一篇个人博客:

默认情况下,JavaScript知道两种类型的作用域:全局作用域和本地作用域。

1
2
3
4
5
6
var a = 1;

function b(x) {
    var c = 2;
    return x * c;
}

在上面的代码中,变量a和函数b可以从代码中的任何地方(即全局)获得。变量c只在b函数范围内可用(即本地)。大多数软件开发人员不会对这种范围灵活性的缺乏感到满意,特别是在大型程序中。

JavaScript闭包通过将函数绑定到上下文来帮助解决这个问题:

1
2
3
4
5
function a(x) {
    return function b(y) {
        return x + y;
    }
}

这里,函数a返回一个名为b的函数。由于b是在a中定义的,因此它可以自动访问在a中定义的任何内容,即本例中的x。这就是为什么b可以在不声明x的情况下返回x + y

1
var c = a(3);

变量c被赋值为参数3的a调用的结果。也就是说,函数b的一个实例,其中x = 3。换句话说,c现在是一个等价于:

1
2
3
var c = function b(y) {
    return 3 + y;
}

函数b记得在其上下文中x = 3。因此:

1
var d = c(4);

将值3 + 4赋给d,即7。

注意:如果有人在创建了函数b的实例之后修改了x的值(比如x = 22),这也将反映在b中。因此,稍后调用c(4)将返回22 + 4,即26。

闭包还可以用来限制全局声明的变量和方法的范围:

1
2
3
4
(function () {
    var f ="Some message";
    alert(f);
})();

上面是一个闭包,其中函数没有名称,没有参数,并且被立即调用。突出显示的代码声明了一个全局变量f,它将f的范围限制为闭包。

现在,有一个常见的JavaScript警告,闭包可以帮助:

1
2
3
4
5
var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(x) { return x + i ; }
}

从上面可以看出,大多数人会假设数组a初始化如下:

1
2
3
a[0] = function (x) { return x + 0 ; }
a[1] = function (x) { return x + 1 ; }
a[2] = function (x) { return x + 2 ; }

实际上,这是初始化a的方法,因为上下文中i的最后一个值是2:

1
2
3
a[0] = function (x) { return x + 2 ; }
a[1] = function (x) { return x + 2 ; }
a[2] = function (x) { return x + 2 ; }

解决方案是:

1
2
3
4
5
6
7
var a = new Array();

for (var i=0; i<2; i++) {
    a[i]= function(tmp) {
        return function (x) { return x + tmp ; }
    } (i);
}

在创建函数实例时,参数/变量tmp持有i的更改值的本地副本。


我相信,爱因斯坦没有说直接期望为我们挑选任何深奥的头脑风暴,运行在6岁徒劳的试图让这些"疯狂"(更糟糕的是他们无聊)是什么东西是幼稚的思想:)如果我是六岁,我不想有这样的父母或者不会让友谊这样无聊的慈善家,不好意思:)

无论如何,对婴儿来说,结束只是一个拥抱,我想,不管你用什么方式来解释:)当你拥抱你的朋友时,你们都会分享你们此刻拥有的一切。这是一种仪式,一旦你拥抱了一个人,你就表示出她的信任和愿意让她和你一起做很多你不允许的事情,而且会对其他人隐瞒。这是友谊的表现:)。

我真的不知道如何向5-6岁的婴儿解释。我不认为他们会欣赏任何JavaScript代码片段,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Baby(){
    this.iTrustYou = true;
}

Baby.prototype.hug = function (baby) {
    var smiles = 0;

    if (baby.iTrustYou) {
        return function() {
            smiles++;
            alert(smiles);
        };
    }
};

var
   arman = new Baby("Arman"),
   morgan = new Baby("Morgana");

var hug = arman.hug(morgan);
hug();
hug();

对孩子只有:

闭包是拥抱

错误是飞

吻吻!:)


皮诺曹:1883年的闭包(比JavaScript早一个多世纪)

我认为这可以最好地解释给一个6岁的孩子和一个很好的冒险…《皮诺乔历险记》中,皮诺乔被一条巨大的狗鲨吃掉了……

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
var tellStoryOfPinocchio = function(original) {

  // Prepare for exciting things to happen
  var pinocchioFindsMisterGeppetto;
  var happyEnding;

  // The story starts where Pinocchio searches for his 'father'
  var pinocchio = {
    name: 'Pinocchio',
    location: 'in the sea',
    noseLength: 2
  };

  // Is it a dog... is it a fish...
  // The dogfish appears, however there is no such concept as the belly
  // of the monster, there is just a monster...
  var terribleDogfish = {
    swallowWhole: function(snack) {
      // The swallowing of Pinocchio introduces a new environment (for the
      // things happening inside it)...
      // The BELLY closure... with all of its guts and attributes
      var mysteriousLightLocation = 'at Gepetto\'s ship';

      // Yes: in my version of the story the monsters mouth is directly
      // connected to its belly... This might explain the low ratings
      // I had for biology...
      var mouthLocation = 'in the monsters mouth and then outside';

      var puppet = snack;


      puppet.location = 'inside the belly';
      alert(snack.name + ' is swallowed by the terrible dogfish...');

      // Being inside the belly, Pinocchio can now experience new adventures inside it
      pinocchioFindsMisterGeppetto = function() {
        // The event of Pinocchio finding Mister Geppetto happens inside the
        // belly and so it makes sence that it refers to the things inside
        // the belly (closure) like the mysterious light and of course the
        // hero Pinocchio himself!
        alert(puppet.name + ' sees a mysterious light (also in the belly of the dogfish) in the distance and swims to it to find Mister Geppetto! He survived on ship supplies for two years after being swallowed himself. ');
        puppet.location = mysteriousLightLocation;

        alert(puppet.name + ' tells Mister Geppetto he missed him every single day! ');
        puppet.noseLength++;
      }

      happyEnding = function() {
        // The escape of Pinocchio and Mister Geppetto happens inside the belly:
        // it refers to Pinocchio and the mouth of the beast.
        alert('After finding Mister Gepetto, ' + puppet.name + ' and Mister Gepetto travel to the mouth of the monster.');
        alert('The monster sleeps with its mouth open above the surface of the water. They escape through its mouth. ');
        puppet.location = mouthLocation;
        if (original) {
          alert(puppet.name + ' is eventually hanged for his innumerable faults. ');
        } else {
          alert(puppet.name + ' is eventually turned into a real boy and they all lived happily ever after...');
        }
      }
    }
  }

  alert('Once upon a time...');
  alert('Fast forward to the moment that Pinocchio is searching for his \'father\'...');
  alert('Pinocchio is ' + pinocchio.location + '.');
  terribleDogfish.swallowWhole(pinocchio);
  alert('Pinocchio is ' + pinocchio.location + '.');
  pinocchioFindsMisterGeppetto();
  alert('Pinocchio is ' + pinocchio.location + '.');
  happyEnding();
  alert('Pinocchio is ' + pinocchio.location + '.');

  if (pinocchio.noseLength > 2)
    console.log('Hmmm... apparently a little white lie was told. ');
}

tellStoryOfPinocchio(false);


如果你想向一个6岁的孩子解释它,那么你必须找到一些非常简单的东西,而且没有代码。

只要告诉孩子,他是"开放的",这意味着他能够与其他一些人,他的朋友建立关系。在某一时刻,他已经有了坚定的朋友(我们可以知道他朋友的名字),这是一个结束语。如果你给他和他的朋友们拍照,那么他相对于他的友谊能力来说是"封闭的"。但总的来说,他是"开放的"。在他的一生中,他会有许多不同的朋友。其中一个集合是闭包。


如果你能很好地理解它,你就能简单地解释它。最简单的方法是从上下文中抽象出来的。撇开代码,甚至编程不谈。一个隐喻的例子会做得更好。

让我们想象一个函数是一个房间,它的墙壁是玻璃做的,但是它们是特殊的玻璃,就像审讯室里的那些。从外面看它们是不透明的,从里面看它们是透明的。它可以是其他房间里的房间,唯一的联系方式是电话。

如果你从外面打电话,你不知道里面是什么,但你知道如果你给里面的人提供一定的信息,他们会完成任务。他们可以看到外面,所以他们可以向你要外面的东西并对它做出改变,但是你不能从外面改变里面是什么,你甚至不知道里面是什么。你打电话给房间里的人看到的是外面的情况,而不是房间里的情况,所以他们会像你在外面做的那样与他们互动。最里面的人可以看到很多东西,但最外面的人甚至不知道最里面的房间的存在。

对于每一个打到内部房间的电话,房间里的人都会记录下关于那个特定电话的信息,他们做得非常好,以至于他们从来不会把一个电话号码和另一个电话号码搞错。

房间是函数,可见性是范围,人做任务是语句,东西是对象,通话是函数调用,通话信息是参数,通话记录是范围实例,最外层的房间是全局对象。


想象一下,在你的城市里有一个非常大的公园,你看到一个叫编码器先生的魔术师用他的魔杖JavaScript在公园的不同角落开始棒球比赛。

当然,每一场棒球比赛都有完全相同的规则,每一场比赛都有自己的记分牌。

当然,一场棒球比赛的分数与其他比赛的分数是完全分开的。

关闭是编码器先生保持他所有神奇的棒球比赛得分分开的特殊方式。


函数在定义它的对象/函数的范围内执行。该函数可以访问在对象/函数中定义的变量。

照字面意思理解……如代码所写:P


A closure is a function having access to the parent scope, even after the parent function has closed.

1
2
3
4
5
6
7
8
9
10
11
var add = (function() {
  var counter = 0;
  return function() {
    return counter += 1;
  }
})();

add();
add();
add();
// The counter is now 3

例子解释道:

变量add被分配给一个自调用函数的返回值。自调用函数只运行一次。它将计数器设置为0(0),并返回一个函数表达式。这样add就变成了一个函数。"奇妙"的部分是它可以访问父范围中的计数器。这称为JavaScript闭包。它使函数具有"私有"变量成为可能。计数器受匿名函数的范围保护,只能使用add函数更改。


闭包是一种方法,通过这种方法,内部函数可以引用父函数已经终止后外部封闭函数中出现的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// A function that generates a new function for adding numbers.
function addGenerator( num ) {
    // Return a simple function for adding two numbers
    // with the first number borrowed from the generator
    return function( toAdd ) {
        return num + toAdd
    };
}

// addFive now contains a function that takes one argument,
// adds five to it, and returns the resulting number.
var addFive = addGenerator( 5 );
// We can see here that the result of the addFive function is 9,
// when passed an argument of 4.
alert( addFive( 4 ) == 9 );

也许您应该考虑面向对象的结构,而不是内部函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
var calculate = {
    number: 0,
    init: function (num) {
        this.number = num;
    },
    add: function (val) {
        this.number += val;
    },
    rem: function (val) {
        this.number -= val;
    }
};

并读取计算结果。number变量,无论如何都需要"return"。


闭包是符合以下三个条件的代码块:

它可以作为一个值传递

任何有价值的人都可以随时执行

它可以引用创建它的上下文中的变量(即,它对变量访问是关闭的,在数学意义上的"封闭")。

("结束"这个词实际上有一个不精确的含义,有些人不认为标准#1是定义的一部分。我想是的。

闭包是函数性语言的主流,但它们也存在于许多其他语言中(例如,Java的匿名内部类)。您可以使用它们做一些很酷的事情:它们允许延迟执行和一些优雅的风格技巧。

作者:Paul Cantrell, @ innig.net/software/ruby/closures-inruby


当内部函数以某种方式对外部函数之外的任何范围可用时,将创建闭包。

例子:

1
2
3
4
5
6
7
8
var outer = function(params){ //Outer function defines a variable called params
    var inner = function(){ // Inner function has access to the params variable of the outer function
        return params;
    }
    return inner; //Return inner function exposing it to outer scope
},
myFunc = outer("myParams");
myFunc(); //Returns"myParams"

调用函数后,它将超出范围。如果该函数包含类似于回调函数的内容,则该回调函数仍然在作用域中。如果回调函数引用父函数直接环境中的某个局部变量,那么您自然会希望回调函数无法访问该变量,并返回未定义的变量。

闭包确保回调函数引用的任何属性都可由该函数使用,即使父函数可能超出了作用域。


闭包是简单的

您可能不应该告诉一个6岁的孩子闭包的事情,但是如果您这样做了,您可能会说闭包使您能够访问在其他函数作用域中声明的变量。

enter image description here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function getA() {
  var a = [];

  // this action happens later,
  // after the function returned
  // the `a` value
  setTimeout(function() {
    a.splice(0, 0, 1, 2, 3, 4, 5);
  });

  return a;
}

var a = getA();
out('What is `a` length?');
out('`a` length is ' + a.length);

setTimeout(function() {
  out('No wait...');
  out('`a` length is ' + a.length);
  out('OK :|')
});
1
2
3
4
5
6
7
8
9
10
11
<pre></pre><脚本>函数(k) {. getelementbyid("输出")。innerHTML += '> ' + k + '';}< /脚本> < /代码> < / pre ></P><div class="suo-content">[collapse title=""]<ul><li>这里的img被用作色盲测试。</li></ul>[/collapse]</div><hr>不包含自由变量的函数称为纯函数包含一个或多个自由变量的函数称为闭包[cc lang="javascript"]var pure = function pure(x){
  return x
  // only own environment is used
}

var foo ="bar"

var closure = function closure(){
  return foo
  // foo is free variable from the outer environment
}

<一口> src: https://leanpub.com/javascriptallongesix/read leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure < /一口>


闭包是JavaScript语言的一种高级特性,但常常被误解。简单地说,闭包是包含函数和对创建函数的环境的引用的对象。然而,为了完全理解闭包,JavaScript语言还有另外两个特性必须首先理解——一流函数和内部函数。

一级函数

在编程语言中,如果函数可以像任何其他数据类型一样被操作,那么它们就被认为是一等公民。例如,可以在运行时构造一流的函数并将其分配给变量。它们还可以由其他函数传递和返回。除了满足前面提到的标准之外,JavaScript函数还有自己的属性和方法。下面的示例展示了一流函数的一些功能。在本例中,创建了两个函数,并将其分配给变量"foo"和"bar"。存储在"foo"中的函数显示一个对话框,而"bar"只返回传递给它的任何参数。这个例子的最后一行做了几件事。首先,以"foo"作为参数调用存储在"bar"中的函数。然后"bar"返回"foo"函数引用。最后,调用返回的"foo"引用,导致"Hello World!""展示。

1
2
3
4
5
6
7
8
9
var foo = function() {
  alert("Hello World!");
};

var bar = function(arg) {
  return arg;
};

bar(foo)();

内部函数

内部函数,也称为嵌套函数,是在另一个函数(称为外部函数)中定义的函数。每次调用外部函数时,都会创建内部函数的实例。下面的示例展示了如何使用内部函数。在本例中,add()是外部函数。在add()内部,定义并调用doAdd()内部函数。

1
2
3
4
5
6
7
8
9
10
function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

内部函数的一个重要特性是它们可以隐式地访问外部函数的作用域。这意味着内部函数可以使用外部函数的变量、参数等。在前面的示例中,add()的"value1"和"value2"参数作为"operand1"和"operand2"参数传递给doAdd()。但是,这是不必要的,因为doAdd()可以直接访问"value1"和"value2"。下面重写了前面的示例,以展示doAdd()如何使用"value1"和"value2"。

1
2
3
4
5
6
7
8
9
10
function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

Creating Closures

A closure is created when an inner function is made accessible from
outside of the function that created it. This typically occurs when an
outer function returns an inner function. When this happens, the
inner function maintains a reference to the environment in which it
was created. This means that it remembers all of the variables (and
their values) that were in scope at the time. The following example
shows how a closure is created and used.

1
2
3
4
5
6
7
8
9
function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

关于这个例子有很多需要注意的地方。

函数的作用是:返回它的内部函数doAdd()。通过返回对内部函数的引用,可以创建闭包。"value1"是add()的一个局部变量,也是doAdd()的一个非局部变量。非局部变量是指既不在局部也不在全局范围内的变量。" value2"是doAdd()的一个局部变量。当调用add(1)时,将创建一个闭包并存储在"increment"中。在闭包的引用环境中,"value1"被绑定到值1。被绑定的变量也被称为封闭变量。这就是名称闭包的由来。当调用increment(2)时,将输入闭包。这意味着调用doAdd(),"value1"变量保存值1。闭包本质上可以看作是创建以下函数。

1
2
3
function increment(value2) {
  return 1 + value2;
}

何时使用闭包

闭包可以用来完成许多事情。它们非常有用用于配置带有参数的回调函数。这部分介绍了闭包可以使您的生活成为a的两种场景开发人员要简单得多。

使用定时器

闭包与setTimeout()和setInterval()函数一起使用时非常有用。更具体地说,闭包允许将参数传递给setTimeout()和setInterval()的回调函数。例如,下面的代码通过调用showMessage()每秒打印一次字符串"some message"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      window.setInterval(showMessage, 1000,"some message<br />");
    });

    function showMessage(message) {
      document.getElementById("message").innerHTML += message;
    }
  </script>
</head>
<body>
  <span id="message"></span>
</body>
</html>

不幸的是,Internet Explorer不支持通过setInterval()传递回调参数。Internet Explorer显示的不是"某些消息",而是"未定义的"(因为实际上没有传递任何值给showMessage())。为了解决这个问题,可以创建一个闭包,它将"message"参数绑定到所需的值。然后可以将闭包用作setInterval()的回调函数。为了说明这个概念,下面重写了前面示例中的JavaScript代码,以使用闭包。

1
2
3
4
5
6
7
8
9
10
11
12
13
window.addEventListener("load", function() {
  var showMessage = getClosure("some message<br />");

  window.setInterval(showMessage, 1000);
});

function getClosure(message) {
  function showMessage() {
    document.getElementById("message").innerHTML += message;
  }

  return showMessage;
}

模仿私人数据

许多面向对象的语言都支持私有成员数据的概念。然而,JavaScript不是一种纯粹的面向对象语言,也不支持私有数据。但是,可以使用闭包模拟私有数据。回想一下,闭包包含对最初创建它的环境的引用——现在已经超出了范围。由于引用环境中的变量只能从闭包函数访问,所以它们本质上是私有数据。

下面的示例显示了一个简单Person类的构造函数。当创建每个人时,通过"name"参数为其指定一个名称。在内部,人员将其名称存储在"_name"变量中。遵循良好的面向对象编程实践,还提供了getName()方法来检索名称。

1
2
3
4
5
6
7
function Person(name) {
  this._name = name;

  this.getName = function() {
    return this._name;
  };
}

Person类仍然存在一个主要问题。因为JavaScript不支持私有数据,所以没有什么可以阻止其他人更改名称。例如,下面的代码创建了一个名为Colin的人,然后将其名称更改为Tom。

1
2
3
4
var person = new Person("Colin");

person._name ="Tom";
// person.getName() now returns"Tom"

就我个人而言,我不喜欢任何人来合法地更改我的名字。为了防止这种情况发生,可以使用闭包将"_name"变量设置为私有。下面使用闭包重写了Person构造函数。注意,"_name"现在是Person构造函数的一个局部变量,而不是对象属性。之所以形成闭包,是因为外部函数Person()通过创建公共getName()方法公开内部函数。

1
2
3
4
5
6
7
function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

现在,当调用getName()时,它保证返回最初传递给构造函数的值。仍然可以向对象添加一个新的"_name"属性,但是只要引用闭包绑定的变量,对象的内部工作就不会受到影响。下面的代码显示"_name"变量确实是私有的。

1
2
3
4
var person = new Person("Colin");

person._name ="Tom";
// person._name is"Tom" but person.getName() returns"Colin"

When Not to Use Closures

It is important to understand how closures work and when to use them.
It is equally important to understand when they are not the right tool
for the job at hand. Overusing closures can cause scripts to execute
slowly and consume unnecessary memory. And because closures are so
simple to create, it is possible to misuse them without even knowing
it. This section covers several scenarios where closures should be
used with caution.

在循环中

在循环中创建闭包可能会产生误导的结果。下面是一个例子。在本例中,创建了三个按钮。当点击"button1"时,应该会显示一个提示,上面写着"已点击按钮1"。"button2"和"button3"也应该显示类似的消息。但是,当这段代码运行时,所有的按钮都显示"click button 4"。这是因为,当单击其中一个按钮时,循环已经执行完毕,循环变量的最终值为4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Closures</title>
  <meta charset="UTF-8" />
  <script>
    window.addEventListener("load", function() {
      for (var i = 1; i < 4; i++) {
        var button = document.getElementById("button" + i);

        button.addEventListener("click", function() {
          alert("Clicked button" + i);
        });
      }
    });
  </script>
</head>
<body>
  <input type="button" id="button1" value="One" />
  <input type="button" id="button2" value="Two" />
  <input type="button" id="button3" value="Three" />
</body>
</html>

要解决这个问题,必须将闭包与实际的循环变量解耦。这可以通过调用一个新函数来实现,该函数反过来创建一个新的引用环境。下面的示例展示了如何实现这一点。循环变量被传递给getHandler()函数。然后getHandler()返回一个独立于原始"for"循环的闭包。

1
2
3
4
5
6
7
8
9
10
11
function getHandler(i) {
  return function handler() {
    alert("Clicked button" + i);
  };
}
window.addEventListener("load", function() {
  for (var i = 1; i < 4; i++) {
    var button = document.getElementById("button" + i);
    button.addEventListener("click", getHandler(i));
  }
});

Unnecessary Use in Constructors

Constructor functions are another common source of closure misuse.
We’ve seen how closures can be used to emulate private data. However,
it is overkill to implement methods as closures if they don’t actually
access the private data. The following example revisits the Person
class, but this time adds a sayHello() method which doesn’t use the
private data.

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };

  this.sayHello = function() {
    alert("Hello!");
  };
}

Each time a Person is instantiated, time is spent creating the
sayHello() method. If many Person objects are created, this becomes a
waste of time. A better approach would be to add sayHello() to the
Person prototype. By adding to the prototype, all Person objects can
share the same method. This saves time in the constructor by not
having to create a closure for each instance. The previous example is
rewritten below with the extraneous closure moved into the prototype.

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
  var _name = name;

  this.getName = function() {
    return _name;
  };
}

Person.prototype.sayHello = function() {
  alert("Hello!");
};

的东西要记住

闭包中包含一个函数和对环境的引用函数是如何创建的。当外部函数公开内部函数时,就形成闭包。闭包可用于方便地将参数传递给回调函数。可以使用闭包模拟私有数据。这在面向对象编程和名称空间设计。构造函数中不应该过度使用闭包。增加了原型是个更好的主意。

链接


考虑到这个问题是简单地向一个6岁的孩子解释,我的答案是:

"当你用JavaScript声明一个函数时,它可以永远访问函数声明之前行中所有可用的变量和函数。这个函数以及它所能访问的所有外部变量和函数就是我们所说的闭包。"


我喜欢Kyle Simpson对闭包的定义:

Closure is when a function is able to remember and access its lexical
scope even when that function is executing outside its lexical scope.

词法范围是指内部范围可以访问其外部范围。

下面是他在《你不知道JS:作用域和放大器》(You Don't Know JS: scope &闭包"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo() {
  var a = 2;

  function bar() {
    console.log( a );
  }
  return bar;
}

function test() {
  var bz = foo();
  bz();
}

// prints 2. Here function bar referred by var bz is outside
// its lexical scope but it can still access it
test();

我认为MDN最好的解释是:

Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure 'remembers' the environment in which it was created.

闭包总是有一个外部函数和一个内部函数。内部函数是所有工作发生的地方,外部函数只是保存创建内部函数的范围的环境。通过这种方式,闭包的内部函数"记住"创建它的环境/范围。最经典的例子是计数器函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
var closure = function() {
  var count = 0;
  return function() {
    count++;
    console.log(count);
  };
};

var counter = closure();

counter() // returns 1
counter() // returns 2
counter() // returns 3

在上面的代码中,count由外部函数(环境函数)保存,以便每次调用counter()时,内部函数(work函数)都可以对它进行增量。


对于一个六岁的孩子…

你知道什么是物体吗?

对象是具有属性和功能的东西。

闭包最重要的一点是,它们允许您用JavaScript创建对象。JavaScript中的对象只是函数和闭包,这些函数和闭包允许JavaScript在对象创建之后存储该对象的属性值。

物件是非常有用的,可以让一切都井井有条。不同的对象可以做不同的工作,而一起工作的对象可以做复杂的事情。

幸运的是JavaScript有用于创建对象的闭包,否则一切都将成为一场混乱的噩梦。


这就是初学者如何将自己的头缠绕在闭包上,就像函数被包裹在函数体(也称为闭包)中一样。

"闭包是一个函数加上到函数创建的作用域的连接"——dr。阿克塞尔Rauschmayer

那会是什么样子呢?下面是一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function newCounter() {
  var counter = 0;
   return function increment() {
    counter += 1;
   }
}

var counter1 = newCounter();
var counter2 = newCounter();

counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3

newCounter对增量关闭,计数器可以被增量引用和访问。

counter1和counter2将跟踪它们自己的值。

简单,但希望有一个清晰的视角,关于所有这些伟大的和先进的答案的结束是什么。


闭包是指函数以在调用函数时不可变的命名空间中定义的方式关闭。

在JavaScript中,当您:

在另一个函数中定义一个函数内部函数在外部函数返回后调用

1
2
3
4
5
6
7
8
9
10
// 'name' is resolved in the namespace created for one invocation of bindMessage
// the processor cannot enter this namespace by the time displayMessage is called
function bindMessage(name, div) {

    function displayMessage() {
        alert('This is ' + name);
    }

    $(div).click(displayMessage);
}

闭包并不难理解。这只是从一个角度看。

我个人喜欢在日常生活中使用它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createCar()
{
    var rawMaterial = [/* lots of object */];
    function transformation(rawMaterials)
    {
       /* lots of changement here */
       return transformedMaterial;
    }
    var transformedMaterial = transformation(rawMaterial);
    function assemblage(transformedMaterial)
    {
        /*Assemblage of parts*/
        return car;
    }
    return assemblage(transformedMaterial);
}

我们只需要在特定的情况下走特定的步骤。至于材料的变换,只有当你有零件时才有用。


从前有一个穴居人

1
function caveman {

他有一块很特别的岩石

1
var rock ="diamond";

你不能自己得到那块石头,因为它在穴居人的私人洞穴里。只有穴居人知道如何找到并得到那块石头。

1
2
3
4
5
6
return {
    getRock: function() {
        return rock;
    }
};
}

幸运的是,他是一个友好的穴居人,如果你愿意等他回来,他会很高兴为你得到它。

1
2
var friend = caveman();
var rock = friend.getRock();

很聪明的穴居人。


最好的方法是循序渐进地解释这些概念:

变量

1
2
console.log(x);
// undefined

这里,undefined是JavaScript表示"我不知道x是什么意思"的方式。

Variables are like tags.

你可以说,标签x指向值42:

1
2
3
var x = 42;
console.log(x);
// 42

现在JavaScript知道x是什么意思了。

You can also re-assign a variable.

让tag x指向一个不同的值:

1
2
3
x = 43;
console.log(x);
// 43

现在x意味着别的东西。

范围

When you make a function, the function has its own"box" for variables.

1
2
3
4
5
6
7
function A() {
  var x = 42;
}

console.log(x);

// undefined

从盒子外面,你看不见盒子里面是什么。

但是从盒子里面,你可以看到盒子外面是什么:

1
2
3
4
5
6
7
var x = 42;

function A() {
  console.log(x);
}

// 42

Inside function A, you have"scope access" to x.

现在如果你有两个并排的盒子:

1
2
3
4
5
6
7
8
9
function A() {
  var x = 42;
}

function B() {
  console.log(x);
}

// undefined

Inside function B, you have no access to variables inside function A.

但是如果你把define function B放在function A里面:

1
2
3
4
5
6
7
8
9
10
11
function A() {

  var x = 42;

  function B() {
    console.log(x);
  }

}

// 42

您现在拥有"范围访问"。

功能

在JavaScript中,你可以通过调用一个函数来运行它:

1
2
3
function A() {
  console.log(42);
}

是这样的:

1
2
3
A();

// 42

函数值

在JavaScript中,你可以把一个标签指向一个函数,就像指向一个数字:

1
2
3
var a = function() {
  console.log(42);
};

Variable a now means a function, you can run it.

1
2
a();
// 42

你也可以传递这个变量:

1
setTimeout(a, 1000);

在1秒(1000毫秒)内,调用指向的函数a:

1
// 42

闭包范围

现在,当您定义函数时,这些函数可以访问它们的外部范围。

当您将函数作为值传递时,如果丢失了访问权,将会很麻烦。

In JavaScript, functions keep their access to outer scope variables.
Even when they are passed around to be run somewhere else.

1
2
3
4
5
6
7
8
9
10
11
12
13
var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  // but you want to run `b` later, rather than right away
  setTimeout(b, 1000);

}

现在发生了什么?

1
// 'Hello!'

或者考虑一下:

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
var c;

var a = function() {

  var text = 'Hello!'

  var b = function() {
    console.log(text);
    // inside function `b`, you have access to `text`
  };

  c = b;

}

// now we are out side of function `a`
// call `a` so the code inside `a` runs
a();

// now `c` has a value that is a function
// because what happened when `a` ran

// when you run `c`
c();

// 'Hello!'

You can still access variables in the closure scope.

即使a已经运行完毕,现在您正在a外部运行c

这里发生的事情在JavaScript中称为"闭包"。


我能想到的解释JavaScript闭包的最简单用例是模块模式。在模块模式中,定义一个函数,然后在所谓的立即调用函数表达式(IIFE)中立即调用它。您在该函数中编写的所有内容都具有私有范围,因为它是在闭包中定义的,因此允许您在JavaScript中"模拟"隐私。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
 var Closure = (function () {
    // This is a closure
    // Any methods, variables and properties you define here are"private"
    // and can't be accessed from outside the function.

    //This is a private variable
    var foo ="";

    //This is a private method
    var method = function(){

    }
})();

另一方面,如果希望在闭包外部显示一个或多个变量或方法,可以在对象文本中返回它们。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var Closure = (function () {
  // This is a closure
  // Any methods, variables and properties you define here are"private"
  // and can't be accessed from outside the function.

  //This is a private variable
  var foo ="";

  //This is a private method
  var method = function(){

  }

  //The method will be accessible from outside the closure
  return {
    method: method
  }

})();

Closure.method();

希望它可以帮助。问候,


闭包基本上是创建两个东西:——一个函数-只有该函数可以访问的私有范围

这就像在函数周围涂上一层涂层。

所以对于一个6岁的孩子,可以用一个类比来解释。假设我造了一个机器人。那个机器人能做很多事情。其中,我给它编了个程序,让它计算他在天空中看到的鸟的数量。每次他看到25只鸟,他应该告诉我从一开始他看到了多少只鸟。

除非他告诉我,否则我不知道他见过多少只鸟。只有他知道。这是私有范围。这基本上就是机器人的记忆。假设我给了他4 GB。

返回函数是告诉我他见过多少只鸟。我也创造了这个。

这个类比有点糟糕,但我想有人可以改进一下。


我认为有必要退后一步,研究一个更一般的"闭包"概念——即所谓的"join操作符"。

在数学中,"join"操作符是一个部分有序集合上的函数,它返回大于或等于其参数的最小对象。在符号中,连接[a,b] = d使d >= a, d >= b,但不存在e使d > e >= a或d > e >= b。

所以连接给你的是比部分更大的最小的东西。

现在,注意JavaScript作用域是一个部分有序结构。所以有一个合理的连接概念。特别是,作用域连接是比原始作用域大的最小作用域。这个范围称为闭包。

因此,变量a、b、c的闭包是将a、b和c带入作用域的最小作用域(在程序作用域的格中!)。


我以前读过所有这些,它们都很有用。有些人非常接近于得到简单的解释,然后变得复杂或保持抽象,违背了目的,未能展示一个非常简单的现实世界的用途。

尽管通过梳理所有的示例和解释,您可以很好地了解闭包是什么,而不是通过注释和代码,但是我仍然不满意一个非常简单的例子,它帮助我在不太复杂的情况下获得闭包的有用性。我妻子想学编程,我想我需要在这里不仅展示什么,而且展示为什么,以及如何展示。

我不确定一个6岁的孩子能不能理解,但我认为它可能更接近于用真实世界的方式来演示一个简单的例子,这个例子可能会很有用,也很容易理解。

最好(或最接近最简单)的例子之一是复述Morris的闭包for Dummies。

把"SayHi2Bob"的概念更进一步,你可以从阅读所有答案中收集到两个基本的东西:

闭包可以访问包含函数的变量。闭包保存在它们自己的内存空间中(因此对于各种op-y实例化都很有用)

为了向自己证明这一点,我做了一个小小提琴:

http://jsfiddle.net/9ZMyr/2/

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
function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  console.log(text);
  var sayAlert = function () {
      alert(text);
  }
  return sayAlert;
}

sayHello();
/* This will write 'Hello undefined' to the console (in Chrome anyway),
but will not alert though since it returns a function handle to nothing).
Since no handle or reference is created, I imagine a good js engine would
destroy/dispose of the internal sayAlert function once it completes. */


// Create a handle/reference/instance of sayHello() using the name 'Bob'
sayHelloBob = sayHello('Bob');
sayHelloBob();

// Create another handle or reference to sayHello with a different name
sayHelloGerry = sayHello('Gerry');
sayHelloGerry();

/* Now calling them again demonstrates that each handle or reference contains its own
unique local variable memory space. They remain in memory 'forever'
(or until your computer/browser explode) */

sayHelloBob();
sayHelloGerry();

这演示了关于闭包的两个基本概念。

简单地说,为了解释为什么这是有用的,我有一个基本函数,我可以对它进行引用或处理,其中包含保存在内存引用中的惟一数据。我不需要每次说别人的名字时都重写这个函数。我已经封装了这个例程并使其可重用。

对我来说,这至少引出了构造函数、oop实践、单例与实例化实例的基本概念。

如果您从这开始一个新手,那么您就可以转向更复杂的基于对象属性/成员的调用,希望这些概念能够实现。


闭包这个词简单地指能够访问函数(six: box)中关闭的对象(six: things) (six: private)。即使函数(6岁:框)超出范围(6岁:发送远)。


让我们从这里开始,正如在MDN上定义的那样:闭包是引用独立(自由)变量的函数(在本地使用,但在封闭范围中定义的变量)。换句话说,这些函数"记住"创建它们的环境。

词法作用域考虑以下:

1
2
3
4
5
6
7
8
function init() {
  var name = 'Mozilla'; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
    alert(name); // use variable declared in the parent function    
  }
  displayName();    
}
init();

init()创建一个名为name的局部变量和一个名为displayName()的函数。displayName()函数是一个内部函数,定义在init()中,并且只能在init()函数的主体中使用。函数本身没有局部变量。但是,由于内部函数可以访问外部函数的变量,所以displayName()可以访问父函数init()中声明的变量名。

1
2
3
4
5
6
7
8
function init() {
    var name ="Mozilla"; // name is a local variable created by init
    function displayName() { // displayName() is the inner function, a closure
        alert (name); // displayName() uses variable declared in the parent function    
    }
    displayName();    
}
init();

运行代码,注意displayName()函数中的alert()语句成功地显示了name变量的值,该变量在其父函数中声明。这是词法作用域的一个例子,它描述了当函数嵌套时解析器如何解析变量名。单词"词法"指词法作用域使用源代码中声明变量的位置来确定变量的可用位置。嵌套函数可以访问在其外部作用域中声明的变量。

关闭现在考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

运行这段代码的效果与上面init()函数的前一个示例完全相同:这一次,字符串"Mozilla"将显示在JavaScript警告框中。有趣的是,displayName()内部函数在执行之前从外部函数返回。

乍一看,这段代码仍然有效似乎不太直观。在一些编程语言中,函数中的局部变量只在函数执行期间存在。一旦makeFunc()执行完毕,您可能会认为name变量将不再可访问。然而,由于代码仍然按预期工作,JavaScript显然不是这样。

原因是函数使用JavaScript表单闭包。闭包是函数和声明该函数的词法环境的组合。该环境由创建闭包时处于作用域内的任何局部变量组成。在本例中,myFunc是对运行makeFunc时创建的函数displayName实例的引用。displayName实例维护对其词法环境的引用,变量名存在于其中。因此,当调用myFunc时,变量名仍然可用,并将"Mozilla"传递给alert。

这里有一个稍微有趣的例子——makeAdder函数:

1
2
3
4
5
6
7
8
9
10
11
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

在本例中,我们定义了一个函数makeAdder(x),它接受单个参数x并返回一个新函数。它返回的函数接受一个参数y,并返回x和y的和。

实际上,makeAdder是一个函数工厂——它创建的函数可以为它们的参数添加特定的值。在上面的例子中,我们使用函数工厂来创建两个新函数——一个向参数添加5,另一个添加10。

add5和add10都是闭包。它们共享相同的函数体定义,但存储不同的词汇环境。在add5的词法环境中,x是5,而在add10的词法环境中,x是10。

实际的闭包

闭包非常有用,因为它们允许您将一些数据(词法环境)与操作该数据的函数关联起来。这与面向对象编程有明显的相似之处,在面向对象编程中,对象允许我们将一些数据(对象的属性)与一个或多个方法关联起来。

因此,您可以在通常只使用一个方法的对象的任何地方使用闭包。

您可能希望这样做的情况在web上特别常见。我们用前端JavaScript编写的大部分代码都是基于事件的——我们定义一些行为,然后将其附加到由用户触发的事件(例如单击或按键)上。我们的代码通常作为回调附加:一个响应事件而执行的函数。

例如,假设我们希望向页面添加一些按钮来调整文本大小。一种方法是指定body元素的字体大小(以像素为单位),然后使用相关的em单元设置页面上其他元素的大小(如标题):

1
2
3
4
5
6
7
8
9
10
11
12
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

我们的交互式文本大小按钮可以更改body元素的字体大小属性,由于相关单元的关系,页面上的其他元素将进行调整。JavaScript:

1
2
3
4
5
6
7
8
9
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12、size14和size16现在是分别将正文文本大小调整为12、14和16像素的函数。我们可以将它们附加到按钮(在本例中为链接)上,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

12
14
16


function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;

有关闭包的更多信息,请访问MDN上的链接


我对闭包的看法:

闭包可以比作书架上放着书签的书。

假设你读过一本书,你喜欢书中的某一页。你在那一页放上书签来跟踪它。

现在,一旦你读完这本书,你就不再需要这本书了,除了,你想要访问那一页。你可以直接剪掉这一页,但这样你就失去了故事的背景。所以你把书和书签一起放回书架上。

这类似于闭包。书是外部函数,页面是内部函数,它从外部函数返回。书签是对页面的引用,而故事的上下文是需要保留的词汇范围。书架是一个函数栈,只有当你抓住书页时,才能把旧书清理干净。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
function book() {
   var pages = [....]; //array of pages in your book
   var bookMarkedPage = 20; //bookmarked page number
   function getPage(){
       return pages[bookMarkedPage];
   }
   return getPage;
}

var myBook = book(),
    myPage = myBook.getPage();

当您运行book()函数时,您是在堆栈中为要运行的函数分配内存。但是由于它返回一个函数,内存不能被释放,因为内部函数可以访问外部上下文中的变量,在本例中是"pages"和"bookMarkedPage"。

因此,有效地调用book()将返回对闭包i的引用。e不仅是一个函数,而且是对书及其上下文的引用,即对函数getPage、页面状态和bookMarkedPage变量的引用。

以下几点需要考虑:

点1:书架就像函数堆栈一样,空间有限,所以要明智地使用它。

点2:想想这样一个事实,当你只想追踪一页内容时,你是否需要抓住整本书。您可以释放部分内存,方法是在返回闭包时不存储书中的所有页面。

这是我对闭包的看法。希望它能有所帮助,如果有人认为这是不正确的,请让我知道,因为我非常有兴趣了解更多关于作用域和闭包的内容!


也……也许我们应该放你27岁的朋友一马,因为"闭包"的整个概念真的是(!)巫术!

我的意思是:(a)直觉上,你并不期待……(b)当有人花时间向你解释时,你肯定不希望它奏效!

直觉告诉你"这一定是无稽之谈……肯定会导致某种语法错误什么的!"实际上,您如何(!)"从‘中间’的任何位置拉出一个函数",这样您就可以(仍然!)实际上,已经对"where ever-it-was-at?!"

当你最终意识到这样的事情是可能的,然后……确定……任何人事后的反应都会是:"哇——哇——哇(!)kew-el-l-l-l…(! ! !)"

但首先要克服的是一个"巨大的反直觉障碍"。直觉给了你很多非常可信的期望,认为这样的事情"当然是完全荒谬的,因此是完全不可能的"。

就像我说的:"这是巫术。"


最简单、最短、最容易理解的答案:

闭包是一段代码,其中每一行都可以引用具有相同变量名的相同变量集。

如果"this"的意思与其他地方不同,那么您就知道它是两个不同的闭包。


闭包可以是私有变量,也可以是公共变量或函数。

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
var ClusureDemo = function() {
    //privare variables
    var localVa1, localVa2;

    //private functions
    var setVaOne = function(newVa) {
        localVa1 = newVa;
    },
    setVaTwo = function(newVa) {
        localVa2 = newVa;
    },
    getVaOne = function() {
        return localVa1;
    },
    getVaTwo = function() {
        return localVa2;
    };

    return {
        //public variables and functions
        outVaOne : localVa1,
        outVaTwo : localVa2,
        setVaOne : setVaOne,
        setVaTwo : setVaTwo,
        getVaOne : getVaOne,
        getVaTwo : getVaTwo
    };
};

//Test Demo
var app = new ClusureDemo();
app.outVaOne = 'Hello Variable One';
app.outVaTwo = 'Hello Variable Two';
app.setVaOne(app.outVaOne);
app.setVaTwo(app.outVaTwo);

alert(app.getVaOne());
alert(app.getVaTwo());

演示


闭包就是一个函数可以访问它的外部作用域,即使这个作用域的函数已经执行完毕。例子:

1
2
3
4
5
6
7
8
9
function multiplier(n) {
    function multiply(x) {
          return n*x;
    }
    return mutliply;
}

var 10xmultiplier = multiplier(10);
var x = 10xmultiplier(5); // x= 50

我们可以看到,即使乘数已经执行完毕,内部函数multiply仍然可以访问x的值,在这个例子中x是10。

闭包的一个非常常见的用法是局部套用(与上面的示例相同),在这里,我们逐步为函数添加参数,而不是一次提供所有参数。

我们可以实现这一点,因为Javascript(除了原型OOP)允许as以函数方式编程,其中高阶函数可以将其他函数作为参数(fisrt类函数)。维基百科中的函数式编程

我强烈推荐您阅读凯尔·辛普森(Kyle Simpson)的这本书:2这本书是关于闭包的系列丛书的一部分,名为scope和closure。你不知道js:免费阅读github


闭包是一个函数,它可以访问它所定义的环境中的信息。

对一些人来说,信息是创建时环境中的价值。对于其他人,信息是创建时环境中的变量。

如果闭包引用的词法环境属于已退出的函数,那么(在闭包引用环境中的变量的情况下)这些词法变量将继续存在,供闭包引用。

闭包可以看作全局变量的一种特殊情况——仅为函数创建一个私有副本。

或者可以把它看作一种方法,其中环境是对象的特定实例,对象的属性是环境中的变量。

前者(闭包作为环境)类似于后者,其中环境副本是传递给前者中的每个函数的上下文变量,实例变量构成后者中的上下文变量。

因此,闭包是一种调用函数的方法,而不必在方法调用中显式地将上下文指定为参数或对象。

1
2
3
var closure = createclosure(varForClosure);
closure(param1);  // closure has access to whatever createclosure gave it access to,
                  // including the parameter storing varForClosure.

1
2
var contextvar = varForClosure; // use a struct for storing more than one..
contextclosure(contextvar, param1);

1
2
var contextobj = new contextclass(varForClosure);
contextobj->objclosure(param1);

对于可维护的代码,我推荐使用面向对象的方法。然而,对于一组快速简单的任务(例如创建回调),闭包可以变得更自然、更清晰,特别是在lamda或匿名函数的上下文中。