题 JavaScript闭包如何工作?


您如何向知道其所包含概念的人(例如函数,变量等)解释JavaScript闭包,但不了解闭包本身?

我见过 Scheme示例 在维基百科上给出,但不幸的是它没有帮助。


7654


起源


我对这些和许多答案的问题是,他们从抽象的,理论的角度来看待它,而不是首先解释为什么在Javascript中需要闭包以及使用它们的实际情况。你最终得到了一篇文章,你必须一直在思考,“但是,为什么?”。我只想从以下开始:闭包是处理以下两种JavaScript现实的简洁方法:a。范围是功能级别,而不是块级别,b。您在JavaScript中实践的大部分内容都是异步/事件驱动的。 - Jeremy Burton
@Redsandro首先,它使事件驱动的代码更容易编写。我可能会在页面加载时触发一个函数来确定有关HTML或可用功能的细节。我可以在该函数中定义和设置一个处理程序,并在每次调用处理程序时都可以使用所有上下文信息,而无需重新查询它。解决问题一次,在需要该处理程序的每个页面上重用,减少处理程序重新调用的开销。您是否曾看到相同的数据在没有它们的语言中重新映射两次?闭包使得避免这种事情变得容易多了。 - Erik Reppen
对于Java程序员来说,简短的回答是它的功能相当于内部类。内部类还包含一个指向外部类实例的隐式指针,并且用于多个相同的目的(即创建事件处理程序)。 - Boris van Schooten
从这里开始理解这个更好: javascriptissexy.com/understand-javascript-closures-with-ease。还需要一个 关闭 在阅读其他答案后关闭。 :) - Akhoy
我发现这个实用的例子非常有用: youtube.com/watch?v=w1s9PgtEoJs - Abhi


答案:


适用于初学者的JavaScript闭包

莫里斯于星期二提交,2006-02-21 10:19。社区编辑以来。

闭包不是魔术

这个页面解释了闭包,以便程序员可以理解它们 - 使用有效的JavaScript代码。它不适合大师或功能程序员。

关闭是 不难 一旦核心概念被理解,就要理解。但是,通过阅读任何学术论文或有关它们的学术信息,它们是不可能理解的!

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

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

闭包的一个例子

两句话摘要:

  • 封闭是一种支撑方式 一流的功能;它是一个表达式,可以引用其范围内的变量(首次声明时),分配给变量,作为参数传递给函数,或作为函数结果返回。

  • 或者,闭包是一个堆栈帧,它在函数开始执行时分配,并且 没有被释放 函数返回后(好像在堆上而不是堆栈上分配了'堆栈帧')。

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

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的程序员会将函数视为返回指向函数的指针,以及变量 say 和 say2 每个都是一个函数的指针。

指向函数的C指针和函数的JavaScript引用之间存在严重差异。在JavaScript中,您可以将函数引用变量视为同时具有指向函数的指针 同样 作为闭包的隐藏指针。

上面的代码有一个闭包因为匿名函数 function() { console.log(text); } 宣布  另一个功能, sayHello2() 在这个例子中。在JavaScript中,如果您使用 function 关键字在另一个函数中,你正在创建一个闭包。

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

在JavaScript中,如果在另一个函数中声明一个函数,那么从您调用的函数返回后,本地变量仍然可以访问。这在上面说明,因为我们称之为函数 say2()我们回来后 sayHello2()。请注意,我们调用的代码引用变量 text,这是一个 局部变量 功能 sayHello2()

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

看着输出 say2.toString(),我们可以看到代码引用变量 text。匿名函数可以参考 text 它拥有价值 'Hello Bob' 因为局部变量 sayHello2() 被关在一起。

神奇的是,在JavaScript中,函数引用还具有对其创建的闭包的秘密引用 - 类似于委托是方法指针加上对象的秘密引用。

更多例子

出于某种原因,当您阅读它们时,闭包似乎很难理解,但是当您看到一些示例时,它们的工作方式就变得清晰了(我花了一段时间)。 我建议您仔细研究这些示例,直到您了解它们的工作原理。如果你开始使用闭包而没有完全理解它们是如何工作的,你很快就会创建一些非常奇怪的错误!

例3

此示例显示不复制局部变量 - 它们通过引用保留。有点像在外部函数退出时将堆栈帧保留在内存中!

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

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()  - 它与之前的操作完全相同,但没有临时变量。

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中的“变量提升”功能才能理解此示例。

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])} 将一个匿名函数的引用添加到结果数组三次。如果您不熟悉匿名函数,请将其视为:

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

请注意,当您运行该示例时, "item2 undefined" 记录了三次!这是因为就像前面的例子一样,局部变量只有一个闭包 buildList (哪个是 resulti 和 item)。在线上调用匿名函数时 fnlist[j]();它们都使用相同的单个闭包,并使用当前值 i 和 item 在那一个封闭(在哪里 i 有一个值 3 因为循环已经完成,并且 item 有一个值 'item2')。请注意,我们从0开始索引 item 有一个值 item2。而i ++将会增加 i 价值 3

查看变量的块级声明时会发生什么可能会有所帮助 item 使用(通过 let 通过关键字而不是函数范围的变量声明 var 关键词。如果进行了更改,则表示阵列中的每个匿名函数 result 有自己的封闭;运行示例时,输出如下:

item0 undefined
item1 undefined
item2 undefined

如果变量 i 也定义使用 let 代替 var,那么输出是:

item0 1
item1 2
item2 3

例7

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

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中的闭包与函数式语言中的闭包有所不同。

链接

谢谢

如果你有 只是 学习闭包(在这里或其他地方!),然后我对您提出的任何可能使本文更清晰的更改的反馈感兴趣。发送电子邮件至morrisjohns.com(morris_closure @)。请注意,我不是JavaScript的大师 - 也不是关闭。


莫里斯的原帖可以在 互联网档案


6173



辉煌。我特别喜欢:“JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样。” - e-satis
@ e-satisf - 看起来非常棒,“所有局部变量的副本,就像函数退出时一样”具有误导性。它表明变量的值被复制,但实际上它是变量本身的集合,在调用函数后它们不会改变(除了'eval'可能: blog.rakeshpai.me/2008/10/...)。它表明函数必须在创建闭包之前返回,但在闭包可以用作闭包之前不需要返回。 - dlaliberte
这听起来不错:“JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样。”但由于一些原因,这是误导。 (1)函数调用不必为了创建闭包而退出。 (2)它不是副本 值 局部变量但变量本身。 (3)它没有说谁有权访问这些变量。 - dlaliberte
示例5显示了“gotcha”,其中代码不能按预期工作。但它没有显示如何解决它。 这是另一个答案 显示了一种方法。 - Matt
我喜欢这篇文章的开头是大胆的大写字母说“闭包不是魔术”并结束了它的第一个例子“魔术就是在JavaScript中一个函数引用也有一个秘密引用它所创建的闭包”。 - Andrew Macheret


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

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

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

 关闭。功能不必 返回 为了被称为​​封闭。 只需访问直接词法范围之外的变量就可以创建一个闭包

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 仍然可以参考 x 和 tmp,即使它不再直接在范围内。

但是,自从 tmp 仍然在里面闲逛 bar关闭,它也在增加。每次打电话都会增加 bar

闭包最简单的例子是:

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用一个对象,它返回的闭包就会 参考 原来的对象!

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 变量!经过几次电话会议 barage.memb 将是2!此引用是HTML对象的内存泄漏的基础。


3808



@feeela:是的,每个JS函数都会创建一个闭包。未引用的变量可能有资格在现代JS引擎中进行垃圾收集,但它不会改变这样一个事实:当您创建执行上下文时,该上下文会引用封闭的执行上下文及其变量,以及该函数是一个有可能被重定位到不同变量范围的对象,同时保留该原始引用。这就是关闭。
@Ali我刚刚发现我提供的jsFiddle实际上并没有证明什么,因为 delete 失败。然而,当执行定义函数的语句时,确定函数将作为[[Scope]]携带的词法环境(并且最终在调用时用作它自己的词法环境的基础)。这意味着该功能 是 关闭执行范围的ENTIRE内容,无论它实际引用哪些值以及它是否超出范围。请查看第13.2和10节 规范 - Asad Saeeduddin
在尝试解释原始类型和引用之前,这是一个很好的答案。它完全错了,并且谈论文字被复制,这实际上与任何事情无关。 - Ry-♦
闭包是JavaScript对基于类的面向对象编程的回答。 JS不是基于类的,所以必须找到另一种方法来实现一些无法实现的东西。 - Barth Zalewski
这应该是公认的答案。魔术永远不会发生在内在的功能中。将外部函数赋给变量时会发生这种情况。这为内部函数创建了一个新的执行上下文,因此可以累积“私有变量”。当然,因为分配给外部函数的变量保持了上下文。第一个答案只是让整个事情变得更加复杂而没有解释那里真正发生的事情。 - Albert Gao


前言:这个答案是在问题是:

就像老阿尔伯特所说的那样:“如果你不能解释它给一个六岁的孩子,你自己真的不明白。”我试着向一位27岁的朋友解释JS关闭并完全失败。

任何人都可以认为我是6岁并且对这个主题感兴趣吗?

我很确定我是唯一一个试图从字面上解决初始问题的人之一。从那以后,这个问题多次发生变异,所以我的答案现在看起来非常愚蠢和不合适。希望这个故事的总体思路对某些人来说仍然很有趣。


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

很久以前:

有一位公主......

function princess() {

她生活在一个充满冒险的美好世界。她遇到了她的白马王子,骑着独角兽,与龙搏斗,遇到说话的动物以及许多其他奇幻的东西。

    var adventures = [];

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

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

    /* ... */

但她总是要回到她沉闷的家务和成年人的世界。

    return {

而且她经常会告诉他们最近作为公主的惊人冒险经历。

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

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

var littleGirl = princess();

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

littleGirl.story();

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

但我们知道真相;里面有公主的小女孩......

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


2253



我真的很喜欢这个解释。对于那些阅读并且不遵循的人来说,类比是这样的:princess()函数是一个包含私有数据的复杂范围。在函数外部,无法查看或访问私有数据。公主在她的想象中保留了独角兽,龙,冒险等(私人数据),成年人无法亲眼看到它们。但公主的想象力被捕获在封闭中 story() 功能,这是唯一的接口 littleGirl 实例暴露于魔法世界。 - Patrick M
所以在这里 story 是关闭,但有代码 var story = function() {}; return story; 然后 littleGirl 将是关闭。至少那是我得到的印象 MDN使用带有闭包的“私有”方法: “这三个公共职能是关闭共享相同环境的关闭。” - icc97
@ icc97,是的, story 是一个引用在范围内提供的环境的闭包 princess。 princess 也是另一个 默示 关闭,即 princess 和 littleGirl 会分享任何对a的引用 parents 将存在于环境/范围内的数组 littleGirl 存在和 princess 被定义为。 - Jacob Swartwood
@BenjaminKrupp我添加了一个显式的代码注释来显示/暗示在体内有更多的操作 princess 比写的更好。不幸的是,这个故事现在在这个帖子上有点不合适。最初的问题是要求“解释一个5岁的JavaScript关闭”;我的反应是唯一一个甚至试图这样做的人。我不怀疑它会惨遭失败,但至少这种反应可能有机会保持5岁的兴趣。 - Jacob Swartwood
实际上,对我来说,这是完全合理的。而且我必须承认,最后通过使用公主和冒险的故事来理解JS封闭让我觉得有点奇怪。 - Crystallize


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

儿童发展:5至7年  它说:

您的孩子将能够按照两步指示。例如,如果您对您的孩子说:“去厨房给我一个垃圾袋”,他们就能记住这个方向。

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

厨房是一个闭合,有一个局部变量,称为 trashBags。厨房里面有一个叫做的功能 getTrashBag 得到一个垃圾袋并将其退回。

我们可以在JavaScript中编写代码,如下所示:

function makeKitchen () {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

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

var kitchen = makeKitchen();

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

进一步说明闭包有趣的原因:

  • 每一次 makeKitchen() 如果被调用,则会创建一个具有自己独立的新闭包 trashBags
  • trashBags 变量是每个厨房内部的本地变量,外部无法访问,但内部功能上 getTrashBag物业可以访问它。
  • 每个函数调用都会创建一个闭包,但是除非可以从闭包外部调用可以访问闭包内部的内部函数,否则不需要保持闭包。用...返回对象 getTrashBag 功能在这里做到了。

693



实际上,令人困惑的是,makeKitchen功能 呼叫 是实际的闭包,而不是它返回的厨房对象。 - dlaliberte
通过其他人,我发现这个答案是解释闭包的原因和原因的最简单方法。 - Chetabahana
太多的菜单和开胃菜,没有足够的肉和土豆。你可以用一个简短的句子来改进这个答案,例如:“闭包是函数的密封上下文,因为缺少类提供的任何作用域机制。” - Jacob


稻草人

我需要知道点击一个按钮多少次,每三次点击就做一些事......

相当明显的解决方案

// 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;
  }
});
<button id="button">Click Me!</button>

现在这将起作用,但它确实通过添加变量侵入外部范围,变量的唯一目的是跟踪计数。在某些情况下,这可能更好,因为您的外部应用程序可能需要访问此信息。但在这种情况下,我们只改变每三次点击的行为,所以最好是 将此功能包含在事件处理程序中

考虑这个选项

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;
    }
  };
})());
<button id="button">Click Me!</button>

请注意这里的一些事情。

在上面的例子中,我使用JavaScript的闭包行为。 此行为允许任何函数无限期地访问创建它的作用域。 为了实际应用这个,我立即调用一个返回另一个函数的函数,因为我正在返回的函数可以访问内部计数变量(由于上面解释的闭包行为),这导致了一个私有范围供结果使用功能...不是那么简单?让我们淡化它......

一个简单的单线封闭

//          _______________________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); }; })();

返回函数之外的所有变量都可用于返回的函数,但它们不能直接用于返回的函数对象...

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

得到它?因此在我们的主要示例中,count变量包含在闭包中并始终可用于事件处理程序,因此它从单击到单击保持其状态。

此外,这个私有变量状态是 充分 对于读数和分配给它的私有范围变量都是可访问的。

你走了;你现在完全封装了这种行为。

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


527



我不同意你对闭包的定义。它没有理由必须自我调用。说它必须“返回”也有点过于简单(并且不准确)(在这个问题的最佳答案的评论中对此进行了大量讨论) - James Montagne
@James即使你不同意,他的榜样(和整个帖子)也是我见过的最好的之一。虽然这个问题并不老问题,但我完全应该获得+1。 - e-satis
“我需要知道点击按钮的次数,并且每按三次点击一次......” 这引起了我的注意。一个用例和解决方案显示了一个闭包不是一个如此神秘的东西,我们很多人一直在写它们,但并不完全知道正式名称。 - Chris22
很好的例子,因为它显示第二个示例中的“count”保留“count”的值,并且每次单击“element”时不会重置为0。非常翔实! - Adam
+1为 关闭行为。我们能限制吗? 关闭行为 至 功能 在javascript或这个概念也可以应用于该语言的其他结构? - Dziamid


闭包很难解释,因为它们被用来使某些行为工作,每个人都直观地期望工作。我找到解释它们的最佳方式(以及它的方式) 一世 知道他们做了什么)想象没有他们的情况:

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

如果JavaScript会发生什么 没有 知道关闭?只需用它的方法体替换最后一行中的调用(基本上是函数调用的函数),你得到:

console.log(x + 3);

现在,在哪里定义 x?我们没有在当前范围内定义它。唯一的解决方案是让 plus5  携带 它的范围(或者更确切地说,它的父母的范围)。这条路, x 是明确定义的,它绑定到值5。


445



我同意。赋予函数有意义的名称而不是传统的“foobar”名称也对我有很大帮助。语义很重要。 - Ishmael
所以在伪语言中,它基本上就像 alert(x+3, where x = 5)。该 where x = 5 关闭。我对吗? - Jus12
@ Jus12:完全正确。在幕后,闭包只是存储当前变量值(“绑定”)的一些空间,如您的示例所示。 - Konrad Rudolph
这正是一种误导许多人认为是这样的例子 值 在返回的函数中使用,而不是可变变量本身。如果它被改为“return x + = y”,或者更好的是那个和另一个函数“x * = y”,那么很明显没有任何东西被复制。对于习惯于堆叠帧的人来说,想象一下使用堆帧,它可以在函数返回后继续存在。 - Matt
@Matt我不同意。一个例子是 不 应该详尽地记录所有属性。它应该是 还原的 并说明了概念的显着特征。 OP要求一个简单的解释(“一个六岁的孩子”)。接受接受的答案:完全 失败 提供简明扼要的解释,正是因为它试图详尽无遗。 (我同意你的看法,JavaScript的一个重要特性是绑定是通过引用而不是通过值......但同样,一个成功的解释是一个降低到最低限度的解释。) - Konrad Rudolph


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

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

343



顺便说一下,我添加了这个“答案”,澄清了不直接解决原始问题。相反,我希望任何简单的答案(对于一个6岁的孩子)都不会引入关于这个复杂主题的错误观念。例如。上面流行的维基答案说:“关闭就是当你返回内部函数时。”除了语法错误之外,这在技术上是错误的。 - dlaliberte
James,我说封闭是“可能”在封闭函数调用时创建的,因为实现可以推迟创建一个闭包直到某个时候,当它决定绝对需要一个闭包时。如果封闭函数中没有定义内部函数,则不需要闭包。所以也许它可以等到创建第一个内部函数然后从封闭函数的调用上下文中创建一个闭包。 - dlaliberte
@ Beetroot-Beetroot假设我们有一个内部函数,它被传递给另一个使用它的函数 之前 外部函数返回,并假设我们也从外部函数返回相同的内部函数。它在两种情况下都是相同的函数,但是你要说在外部函数返回之前,内部函数被“绑定”到调用堆栈,而在它返回之后,内部函数突然被绑定到闭包。它在两种情况下表现相同;语义是相同的,所以你不只是谈论实现细节吗? - dlaliberte
@ Beetroot-Beetroot,感谢您的反馈,我很高兴我让您思考。我仍然没有看到外部函数的实时上下文和相同的上下文在函数返回时变为闭包(如果我理解你的定义)之间的任何语义差异。内在的功能并不关心。垃圾收集并不关心,因为内部函数以任何一种方式维护对context / closure的引用,而外部函数的调用者只是删除了对调用上下文的引用。但这对人们来说很困惑,也许最好把它称为呼叫上下文。 - dlaliberte
那篇文章很难理解,但我认为它实际上支持我所说的。它说:“闭包是通过返回一个函数对象[...]或直接将对这样一个函数对象的引用分配给一个全局变量来形成的。”我并不是说GC无关紧要。相反,由于GC,并且因为内部函数附加到外部函数的调用上下文(或文章所说的[[scope]],那么外部函数调用是否返回因为与内部的绑定无关紧要功能是重要的。 - dlaliberte