JavaScript闭包如何工作?

e-satis 7653 javascript variables closures scope function

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

我已经看过维基百科上给出的Scheme示例,但遗憾的是它并没有帮助.

Joel Anair.. 6622

适用于初学者的JavaScript闭包

由Morris于星期二提交,2006-02-21 10:19.社区编辑以来.

关闭不是魔术

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

一旦核心概念被弄清楚,关闭并不难理解.但是,通过阅读任何理论或学术导向的解释,他们无法理解!

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

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

两个简短的摘要

  • 当函数(foo)声明其他函数(bar和baz)时,在函数退出时不会销毁在foo中创建的局部变量族.变量只会变得对外界不可见.因此,Foo可以巧妙地返回功能栏和baz,并且他们可以通过这个封闭的变量系列("封闭")继续读取,写入和通信,其他任何人都无法干涉,甚至没有人打电话foo将来再来一次.

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

闭包的一个例子

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

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程序员都会理解如何将函数的引用返回到foo上面代码中的variable().如果你不这样做,那么你需要先了解它,然后才能学习闭包.使用C的程序员会将函数视为返回指向函数的指针,并且变量foofoo每个函数都是指向函数的指针.

指向函数的C指针和对函数的JavaScript引用之间存在严重差异.在JavaScript中,您可以将函数引用变量视为既包含指向函数的指针,包含指向闭包的隐藏指针.

上面的代码有一个闭包,因为在这个例子中,匿名函数bar另一个函数声明的baz.在JavaScript中,如果foo在另一个函数中使用关键字,则创建一个闭包.

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

在JavaScript中,如果在另一个函数中声明一个函数,那么从函数返回后,外部函数的局部变量仍然可以访问.这在上面已经证明了,因为我们say2在返回后调用了该函数say.请注意,我们调用的代码引用变量say2,该变量是函数的局部变量function() { console.log(text); }.

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

查看输出sayHello2(),我们可以看到代码引用变量function.匿名函数可以引用say2()保存值的值,sayHello2()因为局部变量text已在闭包中秘密保持活动状态.

天才是在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

所有三个全局函数都对同一个闭包有一个共同的引用,因为它们都是在一次调用中声明的sayHello2().

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

这三个函数具有对同一闭包的共享访问权限 - say2.toString()定义三个函数时的局部变量.

请注意,在上面的示例中,如果text再次调用,则会创建一个新的闭包(stack-frame!).老text,'Hello Bob',sayHello2()变量被改写新的具有新功能关闭.(在JavaScript中,当你声明另一个函数内部功能,内部功能(S)是/再次重新创建每个外部函数被调用时.)

例5

此示例显示闭包包含在退出之前在外部函数内声明的任何局部变量.请注意,该变量setupSomeGlobals()实际上是在匿名函数之后声明的.首先声明匿名函数,并且当调用该函数时,它可以访问setupSomeGlobals()变量,因为setupSomeGlobals()它在同一范围内(JavaScript执行变量提升).也gLogNumber只是直接调用从中返回的函数引用gIncreaseNumber- 它与先前执行的操作完全相同但没有临时变量.

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"

Tricky:还要注意,gSetNumber变量也在闭包内,并且可以被任何其他可能在其中声明的函数访问alice,或者可以在inside函数内递归访问.

例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

该行将alice三次匿名函数的引用添加到结果数组中.如果您不熟悉匿名函数,请将其视为:

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

请注意,当您运行该示例时,alice会记录三次!这是因为,就像前面的例子中,只有一个用于局部变量关闭sayAlice()()(这是sayAlice(),saysayAlice()).当在线上调用匿名函数时result.push( function() {console.log(item + ' ' + list[i])}; 它们都使用相同的单个闭包,并且它们使用当前值"item2 undefined"buildList在一个闭包内(其中result值为,i因为循环已完成,并且item值为fnlist[j]()).注意我们从0索引,因此i值为item.并且i ++将i增加到该值3.

查看item使用变量的块级声明(通过'item2'关键字)而不是通过关键字的函数范围变量声明时会发生什么可能会有所帮助item.如果进行了更改,则数组中的每个匿名函数item2都有自己的闭包; 运行示例时,输出如下:

item0 undefined
item1 undefined
item2 undefined

如果i还使用3而不是使用定义变量item,则输出为:

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;

摘要

如果一切看起来都不清楚,那么最好的办法是玩这些例子.阅读解释要比理解例子困难得多.我对闭合和堆叠框架等的解释在技术上并不正确 - 它们是用于帮助理解的粗略简化.一旦基本想法被理解,您可以稍后获取详细信息.

最后一点:

  • 每当你let在另一个函数中使用时,都会使用一个闭包.
  • 无论何时var在函数内部使用,都会使用闭包.您result可以在文本中引用函数的局部变量,在i您内部甚至可以使用创建新的局部变量let
  • 函数内部使用var(Function构造函数)时,它不会创建闭包.(新函数不能引用外部函数的局部变量.)
  • JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样.
  • 最好认为闭包始终只是函数的一个入口,并且局部变量被添加到该闭包中.
  • 每次调用带闭包的函数时,都会保留一组新的局部变量(假设函数内部包含函数声明,并且返回对该函数内部的引用,或者以某种方式保留外部引用) ).
  • 两个函数可能看起来像具有相同的源文本,但由于它们的"隐藏"闭包而具有完全不同的行为.我不认为JavaScript代码实际上可以找出函数引用是否有闭包.
  • 如果你正在尝试进行任何动态源代码修改(例如:) function,那么如果eval()是一个闭包它将无法工作(当然,你甚至不会想到在运行时进行源代码字符串替换,但是......).
  • 可以在函数&mdash中的函数声明中获取函数声明,并且可以在多个级别获得闭包.
  • 我认为通常闭包是函数和捕获的变量的术语.请注意,我在本文中没有使用该定义!
  • 我怀疑JavaScript中的闭包与函数式语言中的闭包有所不同.

链接

谢谢

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


莫里斯的原帖可以在互联网档案中找到.

  • 辉煌.我特别喜欢:"JavaScript中的闭包就像保存所有局部变量的副本一样,就像函数退出时一样." (154认同)
  • 我喜欢这篇文章的开头是大胆的大写字母,说"闭包不是魔术",结束了它的第一个例子,"魔术就是在JavaScript中,函数引用也有一个秘密引用它所创建的闭包". (152认同)
  • 这听起来不错:"JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样." 但由于几个原因,这是误导.(1)函数调用不必为了创建闭包而退出.(2)它不是局部变量的*值*的副本,而是变量本身的副本.(3)它没有说谁有权访问这些变量. (64认同)
  • @ e-satisf - 看起来非常棒,"所有局部变量的副本,就像函数退出时一样"是误导性的.它建议复制变量的值,但实际上它是变量本身的集合,在调用函数后不会改变(除了'eval'可能:http://blog.rakeshpai.me/2008/ 10 /理解-EVAL-范围扰流-its.html).它表明函数必须在创建闭包之前返回,但在闭包可以用作闭包之前不需要返回. (36认同)
  • 示例5显示了"陷阱",其中代码不能按预期工作.但它没有显示如何解决它.[这个其他答案](http://stackoverflow.com/a/17200991/706054)显示了一种方法. (21认同)
  • 所以我们可以对闭包这样说:只要在函数内部声明函数,外部函数就在内部函数的范围内,直到内部函数在范围内! (9认同)
  • 例#3是将闭合与javascripts悬挂混合.现在我认为只解释闭合是很困难的,而不会引起吊装行为.这对我最有帮助:`闭包是指独立(自由)变量的函数.换句话说,闭包中定义的函数"记住"创建它的环境.来自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures (5认同)
  • ECMAScript 6可能会在这篇关于闭包的文章中改变一些东西.例如,如果在示例5中使用`let i = 0`而不是`var i = 0`,那么`testList()`将打印出您最初想要的内容. (3认同)
  • 为什么示例3和4说他们会记录667.当我运行它时我分别得到43或42 (3认同)

Ali.. 3877

每当在另一个函数中看到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,它也可以访问tmpfoo.

一个关闭.函数不必返回以便被称为闭包.只需访问直接词法范围之外的变量就可以创建一个闭包.

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.

最简单的闭包示例是:

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

  • @feeela:是的,每个JS函数都会创建一个闭包.未引用的变量可能有资格在现代JS引擎中进行垃圾收集,但它并没有改变这样一个事实:当您创建执行上下文时,该上下文具有对封闭执行上下文及其变量的引用,以及该函数是一个有可能被重定位到不同变量范围的对象,同时保留该原始引用.那是关闭. (19认同)
  • 闭包是JavaScript对基于类的面向对象编程的回答.JS不是基于类的,所以必须找到另一种方法来实现一些无法实现的东西. (7认同)
  • 在尝试解释原始类型和引用之前,这是一个很好的答案.它完全错了,并且谈论文字被复制,这实际上与任何事情无关. (5认同)

Jacob Swartw.. 2308

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

就像老阿尔伯特所说的那样:"如果你不能解释它给一个六岁的孩子,你自己真的不明白."我试着向一位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();

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

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

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

  • 我真的很喜欢这个解释.对于那些阅读并且不遵循的人来说,类比是这样的:princess()函数是一个包含私有数据的复杂范围.在函数外部,无法查看或访问私有数据.公主在她的想象中保留了独角兽,龙,冒险等(私人数据),成年人无法亲眼看到它们.但是,在'story()`函数的闭包中捕获了公主的想象力,这是`littleGirl`实例暴露于魔法世界的唯一界面. (311认同)
  • @ icc97,是的,`story`是一个闭包,引用了`princess`范围内提供的环境.`princess`也是另一个_implied_闭包,即`princess`和`littleGirl`将共享对`parent`数组的任何引用,该数组将存在于'littleGirl`存在且`princess`为的环境/范围内定义. (12认同)
  • @BenjaminKrupp我添加了一个明确的代码注释来显示/暗示`princess`的主体内部的操作比写的更多.不幸的是,这个故事现在在这个帖子上有点不合适.最初的问题是要求"解释一个5岁的JavaScript关闭"; 我的反应是唯一一个甚至试图这样做的人.我不怀疑它会惨遭失败,但至少这种反应可能有机会保持5岁的兴趣. (3认同)
  • 实际上,对我而言,这是完全合理的.而且我必须承认,最后通过使用公主和冒险的故事来理解JS封闭让我觉得有点奇怪. (3认同)

dlaliberte.. 713

认真对待这个问题,我们应该找出一个典型的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();

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函数返回对象就是在这里.

  • 实际上,令人困惑的是,makeKitchen函数*call*是实际的闭包,而不是它返回的厨房对象. (3认同)
  • 通过其他人,我发现这个答案是解释闭包的原因和原因的最简单方法. (3认同)

jondavidjohn.. 549

稻草人

我需要知道点击按钮多少次,并在每三次点击时执行一些操作...

相当明显的解决方案

// 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注意事项)

  • **"我需要知道点击按钮的次数,并且每按三次点击就做一些事情......"**这引起了我的注意.一个用例和解决方案显示了一个闭包不是一个如此神秘的东西,我们很多人一直在写它们,但并不完全知道官方名称. (76认同)
  • @James即使你不以为然,他的榜样(以及整个帖子)都是我见过的最好的之一.虽然这个问题并不老问题,但我完全应该得到+1. (37认同)
  • 我不同意你对闭包的定义.它没有理由必须自我调用.说它必须"返回"也有点过于简单(并且不准确)(在这个问题的最佳答案的评论中对此进行了大量讨论) (9认同)

Konrad Rudol.. 461

闭包很难解释,因为它们被用来做一些每个人都直觉期望工作的行为.我发现解释它们的最佳方式(以及学习它们的方式)是想象没有它们的情况:

    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.

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

dlaliberte.. 352

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

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

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

Max Tkachenk.. 345

好的,6岁的关闭风扇.你想听听关闭的最简单的例子吗?

让我们想象下一个情况:司机坐在车里.那辆车在飞机内.飞机在机场.驾驶员能够接近车外的东西,但是在飞机内部,即使飞机离开机场,也是一种封闭.而已.当您转到27时,请查看更详细的说明或下面的示例.

这是我如何将我的飞机故事转换为代码.

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

  • 好好玩,回答原始海报.我认为这是最好的答案.我打算以类似的方式使用行李:想象你去奶奶的家里然后把你的nintendo DS保护套装在你的箱子内,然后将箱子装在你的背包里,并把游戏卡放在你的背包口袋里,然后你把整个东西放在一个大箱子里,手提箱的口袋里放着更多的游戏卡.当你到达奶奶家时,只要所有外部病例都打开,你就可以在你的DS上玩任何游戏.或者那种效果. (24认同)

Florian Bösc.. 336

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

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

如果你,变量包含在闭包中

  1. var foo=1;或分配它
  2. 写吧 var foo;

如果内部函数(包含在另一个函数中的函数)访问这样的变量而没有在var自己的范围内定义它,它会修改外部闭包中变量的内容.

一个封闭寿命比产生它的功能的运行.如果其他函数使它超出定义它们的闭包/作用域(例如作为返回值),则它们将继续引用该闭包.

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

产量

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

  • 哇,从来不知道你可以在`console.log`中使用字符串替换.如果有其他人感兴趣,还有更多:https://developer.mozilla.org/en-US/docs/DOM/console#Using_string_substitutions (49认同)
  • 函数参数列表中的变量也是闭包的一部分(例如,不仅限于`var`). (6认同)

Nathan Long.. 223

我在一段时间后写了一篇博文,解释了闭包.这就是我所说的关于为什么你想要一个闭包的关闭.

闭包是一种让函数具有持久的私有变量的方法 - 也就是说,只有一个函数知道的变量,它可以跟踪之前运行的信息.

从这个意义上说,他们让一个函数看起来有点像具有私有属性的对象.

全文:

那么封闭东西是什么?

  • 在经过大量的'splaining'之后,我开始最终了解它们的用途.我心想,"哦,它就像一个物体中的私人变量?" 和bam.这是我读到的下一个答案. (7认同)

Matt.. 204

闭包很简单:

以下简单示例涵盖了JavaScript闭包的所有要点.*  

这是一个生产可以增加和增加的计算器的工厂:

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,该变量在返回后很长时间内仍可被该计算器addmultiply函数使用make_calculator.

如果您熟悉堆栈帧,这些计算器似乎很奇怪:如何nmake_calculator返回后继续访问?答案是想象JavaScript不使用"堆栈帧",而是使用"堆帧",它可以在使它们返回的函数调用之后持久存在.

像内部函数addmultiply,其访问外部函数声明的变量**,被称为闭包.

这几乎就是闭包的全部内容.



*例如,它涵盖了另一个答案中给出的"闭包傻瓜"一文中的所有要点,除了示例6,它简单地表明变量可以在声明之前使用,这是一个很好的事实,但要完全与闭包无关.它还涵盖了接受答案所有要点,除了函数将其参数复制到局部变量(命名函数参数)的点(1),以及(2)复制数字创建新数字,但复制对象引用为您提供对同一对象的另一个引用.这些也很好知道但又与封闭完全无关.它也非常类似于这个答案中的例子,但是更短,更抽象.它没有涉及到这一点这个答案或者这个评论,就是说JavaScript很难堵塞当前的内容循环变量的值到内部函数中:"插入"步骤只能通过包含内部函数的辅助函数来完成,并在每次循环迭代时调用.(严格地说,内部函数访问辅助函数的变量副本,而不是插入任何东西.)同样,在创建闭包时非常有用,但不是闭包的一部分或它是如何工作的.由于闭包在ML等函数式语言中的工作方式不同,还存在额外的混淆,其中变量绑定到值而不是存储空间,从而提供了一种持续理解闭包的人(即"插入"方式),即对JavaScript来说简单不对,其中变量总是绑定到存储空间,而永远不会绑定到值.

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

  • @Ronen:由于`first_calculator`是一个对象(不是函数),你不应该在`second_calculator = first_calculator;`中使用括号,因为它是赋值,而不是函数调用.要回答你的问题,那么只有一次调用make_calculator,所以只有一个计算器,而变量first_calculator和second_calculator都会引用同一个计算器,所以答案是3,403,4433,44330. (3认同)

Magne.. 200

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

你知道成年人如何拥有房子,他们称之为家?当一个妈妈有一个孩子时,孩子并没有真正拥有任何东西,对吗?但是它的父母拥有一所房子,所以每当有人问孩子"你家在哪里?"时,他/她就可以回答"那所房子!",并指向其父母的房子.一个"关闭"是孩子总是(即使在国外)能够说它有一个家的能力,即使它真的是父母拥有房子.


Chris S.. 190

你能解释一下5岁的闭嘴吗?*

我仍然认为谷歌的解释非常有效并且简洁:

/*
*    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);?

证明这个例子即使内部函数没有返回也会创建一个闭包

*AC#问题

  • 如果您阅读说明,您会看到您的示例不正确.对innerFunction的调用是在外部函数的范围内,而不是如描述所说,在外部函数返回之后.每当调用outerFunction时,都会创建一个新的innerFunction,然后在范围内使用. (19认同)
  • 代码是"正确的",作为闭包的一个例子,即使它没有解决关于在outerFunction返回后使用闭包的注释部分.所以这不是一个很好的例子.还有许多其他方法可以使用不涉及返回innerFunction的闭包.例如,innerFunction可以被传递给另一个函数,在那里它被立即调用或存储并在稍后调用,并且在所有情况下,它都可以访问在调用它时创建的outerFunction上下文. (9认同)
  • @Moss这不是我的评论,他们是谷歌的开发者 (8认同)
  • 看到innerFunction没有在outerFunction的范围之外引用,解释器是否足够聪明,看不到它需要关闭? (7认同)
  • @syockit不,莫斯错了.无论函数是否逃避定义它的作用域,都会创建一个闭包**,并且无条件地创建对父类词汇环境的引用,使得父作用域中的所有变量都可用于所有函数,无论它们是否为在创建它们的范围之外或之内调用. (4认同)

Chev.. 166

我倾向于通过GOOD/BAD比较来更好地学习.我喜欢看到工作代码后跟有人可能会遇到的非工作代码.我把一个jsFiddle放在一起进行比较,并尝试将差异归结为我能想出的最简单的解释.

关闭完成:

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)调用时的值,因此返回的嵌套函数将始终返回调用n时的值createClosure(n).

关闭错误:

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从定义它的父作用域返回SAME 变量,而不是像第一个示例那样从10个不同的作用域中返回10个不同的变量.最终结果是所有10个函数都从同一范围返回相同的变量.

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

结果

关闭权限
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

关闭错误
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


mykhal.. 158

关于闭包的维基百科:

在计算机科学中,闭包是一个函数以及该函数的非局部名称(自由变量)的引用环境.

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

由于JavaScript中的范围定义构造是一个函数,而不是像许多其他语言中的代码块,我们通常在JavaScript中的闭包是一个函数,它使用已经执行的周围函数中定义的非局部变量.

闭包通常用于创建具有一些隐藏私有数据的函数(但并非总是如此).

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)并在以后执行,每次调用它时都会生成一个数据库函数.每个生成的函数都有自己的隐藏数据库对象.闭包的另一个用法示例是当我们不返回函数,而是包含用于不同目的的多个函数的对象时,每个函数都可以访问相同的数据.


Nathan White.. 132

我整理了一个交互式JavaScript教程来解释闭包是如何工作的. 什么是关闭?

这是一个例子:

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


Tero Tolonen.. 123

孩子们将永远记住他们与父母分享的秘密,即使在父母离开后也是如此.这就是闭包的功能.

JavaScript函数的秘密是私有变量

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

每次调用它时,都会创建局部变量"name"并命名为"Mary".每次函数退出变量都会丢失,名称会被遗忘.

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

但是,在JavaScript中有一个非常特殊的东西,即在其他函数中创建的函数,也可以知道父元素的局部变量,并且只要它们存在就保持它们.

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

因此,只要我们在父函数中,它就可以创建一个或多个子函数,它们从秘密位置共享秘密变量.

但令人遗憾的是,如果孩子也是其父函数的私有变量,那么当父母结束时它也会死亡,并且秘密会随之死亡.

所以为了生活,孩子必须在太晚之前离开

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 

现在,即使玛丽"不再跑步",她的记忆也不会丢失,她的孩子将永远记住她们在一起的时间里分享的名字和其他秘密.

所以,如果你给孩子打电话"爱丽丝",她会回应

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

这就是所有要说的.

  • 这是对我最有意义的解释,因为它没有假设技术术语的重要先验知识.这里最高投票的解释假设不理解闭包的人对"词汇范围"和"执行上下文"等术语有完整而完整的理解 - 虽然我可以从概念上理解这些,但我认为我不是我应该对它们的细节感到满意,并且完全没有行话的解释是什么让闭包最终点击给我,谢谢.作为奖励,我认为它也解释了范围非常简洁. (15认同)

floribon.. 102

我不明白为什么这里的答案如此复杂.

这是一个闭包:

var a = 42;

function b() { return a; }

是.你可能每天都要多次使用它.


没有理由相信闭包是一个复杂的设计黑客来解决具体问题.不,闭包只是从声明函数(不运行)的角度使用来自更高范围的变量.

现在它允许你做什么可以更壮观,看到其他答案.

  • 这个答案似乎不太可能帮助人们疏忽.传统编程语言中的粗略等价物可能是创建b()作为对象的方法,*也*具有私有常量或属性`a`.在我看来,令人惊讶的是JS范围对象有效地将`a`作为属性而不是常量.如果你修改它,你只会注意到这个重要的行为,就像在`return a ++;`中一样 (4认同)
  • 这并没有定义闭包是什么 - 它只是一个使用一个闭包的例子.并没有解决范围结束时发生的细微差别; 当所有范围仍然存在时,我认为没有人对词汇作用域有任何疑问,特别是在全局变量的情况下. (3认同)

someisaac.. 90

dlaliberte的第一点示例:

不仅在返回内部函数时创建闭包.实际上,封闭功能根本不需要返回.您可以将内部函数分配给外部作用域中的变量,或者将其作为参数传递给另一个可以立即使用的函数.因此,封闭函数的闭包可能在调用封闭函数时已经存在,因为任何内部函数在调用它时都可以访问它.

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

  • 仅供参考:运行以上显示=> 9 (4认同)

Rakesh Pai.. 83

闭包是内部函数可以访问其外部函数中的变量的地方.这可能是闭包可以获得的最简单的单行解释.

  • 这只是解释的一半.关于闭包的重要一点是,如果在外部函数退出后仍然引用内部函数,则外部函数的旧值仍然可用于内部函数. (34认同)
  • 实际上,它不是外部函数的旧*值*可用于内部函数,而是旧的*变量*,如果某些函数能够更改它们,它们可能具有新值. (21认同)

Gerardo Lima.. 82

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

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


大智慧.. 78

你正在睡觉,你邀请丹.你告诉Dan带一个XBox控制器.

丹邀请保罗.丹要求保罗带一个控制器.有多少控制器被带到聚会上?

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


John Pick.. 74

JavaScript函数可以访问它们:

  1. 参数
  2. 本地(即本地变量和本地函数)
  3. 环境,包括:
    • 全局,包括DOM
    • 外部功能的任何东西

如果函数访问其环境,则该函数是闭包.

请注意,外部功能不是必需的,但它们确实提供了我在此不讨论的好处.通过访问其环境中的数据,闭包可以使数据保持活动状态.在外部/内部功能的子情形,外功能,可以创建本地数据,并最终退出,但如果任何内部功能的外部函数退出后生存下来,那么内部函数(S)保持外函数的局部数据活.

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

试想一下,堆栈溢出投票行动和投票向下按钮事件为关闭,voteUp_click和voteDown_click,即有机会获得外部变量isVotedUp和isVotedDown,这是全局定义来实现.(为简单起见,我指的是StackOverflow的问题投票按钮,而不是答案投票按钮数组.)

当用户单击VoteUp按钮时,voteUp_click函数检查isVotedDown == true是否确定是否投票或仅取消向下投票.函数voteUp_click是一个闭包,因为它正在访问它的环境.

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
}

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


Arvand.. 73

Closures的作者很好地解释了闭包,解释了为什么我们需要它们,并解释了理解闭包所必需的LexicalEnvironment.
以下是摘要:

如果访问变量,但它不是本地的,该怎么办?像这儿:

在此输入图像描述

在这种情况下,解释器在外部LexicalEnvironment对象中查找变量.

该过程包括两个步骤:

  1. 首先,当创建函数f时,不会在空白空间中创建它.有一个当前的LexicalEnvironment对象.在上面的例子中,它的窗口(在创建函数时a未定义).

在此输入图像描述

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

在此输入图像描述

如果读取变量但在任何地方都找不到,则会生成错误.

嵌套函数

函数可以嵌套在另一个函数中,形成一个LexicalEnvironments链,也可以称为范围链.

在此输入图像描述

因此,函数g可以访问g,a和f.

关闭

外部函数完成后,嵌套函数可能会继续存在:

在此输入图像描述

标记LexicalEnvironments:

在此输入图像描述

如我们所见,this.say是用户对象中的属性,因此在用户完成后它将继续存在.

如果你记得,在this.say创建时,它(作为每个函数)获得this.say.[[Scope]]对当前LexicalEnvironment 的内部引用.因此,当前用户执行的LexicalEnvironment保留在内存中.User的所有变量也都是它的属性,因此它们也被仔细保存,而不是通常的垃圾.

重点是确保如果内部函数将来想要访问外部变量,它就能够这样做.

总结一下:

  1. 内部函数保持对外部LexicalEnvironment的引用.
  2. 即使外部函数完成,内部函数也可以随时访问它的变量.
  3. 浏览器将LexicalEnvironment及其所有属性(变量)保存在内存中,直到有一个引用它的内部函数.

这称为闭包.


grateful.. 58

作为一个6岁的孩子的父亲,目前正在教育幼儿(以及没有接受过正规教育的相对新手,因此需要更正),我认为通过实际操作可以最好地吸取教训.如果这个6岁的孩子已经准备好了解关闭是什么,那么他们已经足够大了,可以自己去.我建议将代码粘贴到jsfiddle.net中,解释一下,然后让他们独自制作一首独特的歌曲.下面的解释性文字可能更适合10岁.

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 + "\n" + result);
    };

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

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

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

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

var person="an old lady";

sing(person);

说明

数据:数据是事实的集合.它可以是数字,单词,测量,观察甚至只是对事物的描述.你不能触摸它,闻它或品尝它.你可以把它写下来,说出来然后听.您可以使用它来使用计算机创造触摸气味和味道.它可以通过使用代码的计算机变得有用.

代码:以上所有内容都称为代码.它是用JavaScript编写的.

JAVASCRIPT:JavaScript是一种语言.像英语或法语或中文是语言.计算机和其他电子处理器可以理解许多语言.要使计算机能够理解JavaScript,它需要一个解释器.想象一下,如果一位只会说俄语的老师来到学校教你的课.当老师说"всесадятся"时,班级不会理解.但幸运的是,你班上有一名俄罗斯学生告诉大家这意味着"每个人都坐下来" - 所以你们都这样做.这个班级就像一台电脑,俄罗斯学生就是翻译.对于JavaScript,最常见的解释器称为浏览器.

浏览器:当您通过计算机,平板电脑或手机连接到Internet访问网站时,您可以使用浏览器.您可能知道的示例包括Internet Explorer,Chrome,Firefox和Safari.浏览器可以理解JavaScript并告诉计算机它需要做什么.JavaScript指令称为函数.

功能:JavaScript中的函数就像工厂一样.它可能是一个只有一台机器的小工厂.或者它可能包含许多其他小工厂,每个工厂都有许多机器在做不同的工作.在现实生活中的衣服工厂里,你可能会有大量的布料和线轴进入,而T恤和牛仔裤也会出现.我们的JavaScript工厂只处理数据,无法缝制,钻孔或熔化金属.在我们的JavaScript工厂中,数据进入并且数据输出.

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

我没有时间去购物,所以功能需要知道我们在冰箱里有什么来做决定.每种成分都有不同的烹饪时间,我们希望机器人同时为所有食物提供热量.我们需要为函数提供关于我们喜欢的数据,该函数可以与冰箱"对话",并且该函数可以控制机器人.

函数通常具有名称,括号和大括号.像这样:

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

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

姓名:您可以根据自己想要的任何字词调用函数.示例"cookMeal"通常是将两个单词连接在一起并在第二个单词的开头给出大写字母 - 但这不是必需的.它不能有空间,它本身不能是一个数字.

父母:"括号"或是()JavaScript功能工厂门上的信箱或街道上的邮箱,用于向工厂发送信息包.例如 cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime),有时可能会标记邮箱,在这种情况下,您知道要提供哪些数据.

支架:看起来像这样的"支架" {}是我们工厂的有色窗户.从工厂内部你可以看到,但从外面你看不到.

上面的长代码示例

我们的代码以单词function开头,所以我们知道它是一个!然后函数的名称唱歌 - 这是我自己对函数的描述.然后括号().括号总是在那里用于函数.有时他们是空的,有时他们有东西.这个有一个字:(person).在这之后有一个像这样的支架{.这标志着函数sing()的开始.它有一个合作伙伴,标志着像这样的唱歌结束}

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

所以这个功能可能与唱歌有关,可能需要一些人的数据.它内部有指令来处理该数据.

现在,在函数sing()之后,接近代码的末尾就是行

var person="an old lady";

VARIABLE:字母var代表"变量".变量就像一个信封.在外面,这个信封被标记为"人".在内部它包含一张纸,上面有我们的功能需要的信息,一些字母和空格连在一起就像一根字符串(它被称为字符串),使一个短语读作"一位老太太".我们的信封可能包含其他类型的东西,如数字(称为整数),指令(称为函数),列表(称为数组).因为这个变量是在所有大括号之外写的{},并且因为当你在大括号内时你可以通过有色窗口看到,所以可以从代码中的任何地方看到这个变量.我们称之为"全局变量".

全球变量:是一个全局变量,意味着如果你将它的价值从"一位老太太"改为"一个年轻人",那么这个将继续成为一个年轻人,直到你决定再次改变它,并且任何其他功能代码可以看出它是一个年轻人.按F12按钮或查看"选项"设置以打开浏览器的开发人员控制台,然后键入"人员"以查看此值.键入person="a young man"以更改它,然后再次键入"人物"以查看它已更改.

在此之后我们有了这条线

sing(person);

这一行正在调用该函数,就好像它正在调用一只狗一样

"来,来吧,让的人!"

当浏览器加载到达此行的JavaScript代码时,它将启动该功能.我把这行放在最后,以确保浏览器具有运行它所需的所有信息.

功能定义动作 - 主要功能是唱歌.它包含一个名为firstPart的变量,它适用于歌曲中关于适用于歌曲每节经文的人的歌唱:"有"+人+"吞下".如果您在控制台中键入firstPart,则无法得到答案,因为该变量已锁定在函数中 - 浏览器无法在大括号的有色窗口内看到.

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

闭包都知道sing()函数的变量名为firstPart是什么,因为它们可以从它们的有色窗口中看到.

关闭之后就行了

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

sing()函数将按照给定的顺序调用每个函数.然后将完成sing()函数的工作.


dmi3y.. 53

好的,和一个6岁的孩子交谈,我可能会使用以下关联.

想象一下 - 你在整个房子里和你的小兄弟姐妹一起玩耍,然后你带着你的玩具四处走动,把它们带进了你哥哥的房间.过了一会儿,你的哥哥从学校回来,然后去了他的房间,他把它锁在里面,所以现在你不能再直接进入那里留下的玩具了.但你可以敲门,问你的兄弟那些玩具.这被称为玩具的封闭 ; 你的兄弟和好了你,他现在是在外面的范围.

比较一个门被草稿锁定而内部没人(一般功能执行)的情况,然后发生一些局部火灾并烧毁房间(垃圾收集器:D),然后建造一个新房间,现在你可以离开那里有另一个玩具(新功能实例),但从来没有得到第一个房间实例中留下的相同玩具.

对于一个高级孩子,我会提出如下的内容.它并不完美,但它让你感觉它是什么:

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 && 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来玩它.


Stupid Stupi.. 49

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

函数可以返回数据.您可以从函数返回的一种数据是另一种功能.当返回该新函数时,创建它的函数中使用的所有变量和参数都不会消失.相反,该父功能"关闭".换句话说,没有什么可以看到它内部并看到它使用的变量,除了它返回的函数.这个新函数具有特殊的能力,可以回顾创建它的函数并查看其中的数据.

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

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

无论何时在较大范围内创建较小的范围,较小的范围始终都能够查看较大范围内的范围.


srgstm.. 47

JavaScript中的函数不仅仅是对一组指令的引用(如在C语言中),而且它还包括一个隐藏的数据结构,该结构由对它使用的所有非局部变量(捕获的变量)的引用组成.这种两件式函数称为闭包.JavaScript中的每个函数都可以被视为闭包.

闭包是具有状态的功能.它有点类似于"this",意思是"this"也为函数提供状态但函数和"this"是单独的对象("t​​his"只是一个奇特的参数,并且是将它永久绑定到a的唯一方法函数是创建一个闭包).虽然"this"和函数总是分开存在,但函数不能与其闭包分开,并且语言无法访问捕获的变量.

因为词法嵌套函数引用的所有这些外部变量实际上是其词法封闭函数链中的局部变量(全局变量可以假定为某些根函数的局部变量),并且函数的每次执行都会创建新的实例它的局部变量,它遵循一个函数的每次执行返回(或以其他方式将其转移出来,例如将其注册为回调)嵌套函数创建一个新的闭包(具有其自己的可能唯一的一组引用的非局部变量,表示其执行上下文).

此外,必须要理解的是,JavaScript中的局部变量不是在堆栈帧上创建的,而是在堆上创建的,只有在没有人引用它们时才会被销毁.当函数返回时,对其局部变量的引用会递减,但如果在当前执行期间它们成为闭包的一部分并且仍然由其词法嵌套函数引用(它们仅在引用时才会发生),它们仍然可以为非null.这些嵌套函数被返回或以其他方式传递给某些外部代码).

一个例子:

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


Michael Dzie.. 47

也许除了最早期的六岁孩子之外,还有一点点,但是有一些例子帮助我在JavaScript中实现了关闭概念.

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

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

警告:猴子

在上面的例子中,调用了outerFunction,后者又调用了innerFunction.注意innerVar如何可用于innerFunction,通过正确警告outerVar的值来证明.

现在考虑以下内容:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

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

警告:猴子

referenceToInnerFunction设置为outerFunction(),它只返回对innerFunction的引用.当调用referenceToInnerFunction时,它返回outerVar.同样,如上所述,这表明innerFunction可以访问outerVar的变量outerVunction.此外,有趣的是,即使在outerFunction完成执行后,它仍保留此访问权限.

在这里,事情变得非常有趣.如果我们要删除outerFunction,比如将其设置为null,您可能会认为referenceToInnerFunction将失去对outerVar值的访问权限.但这种情况并非如此.

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放在outerFunction中来创建闭包时,innerFunction将对outerFunction的作用域(其变量和函数)的引用添加到其作用域链中.这意味着innerFunction有一个指向或引用所有outerFunction的变量,包括outerVar.因此,即使在outerFunction完成执行时,或者即使它被删除或设置为null,其范围中的变量(如outerVar)也会留在内存中,因为在返回到的内部函数部分对它们的未完成引用referenceToInnerFunction.要从内存中真正释放outerVar和其余的outerFunction变量,你必须摆脱对它们的这一杰出引用,比如将referenceToInnerFunction设置为null.

//////////

关于闭包的另外两件事要注意.首先,闭包将始终可以访问其包含函数的最后一个值.

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

警告:大猩猩

其次,当创建一个闭包时,它会保留对其所有封闭函数的变量和函数的引用; 它无法挑选.但是,应该谨慎使用封闭装置,或者至少要小心使用,因为它们可能会占用大量内存; 在包含函数完成执行后很长时间内,很多变量都可以保存在内存中.


大智慧.. 44

我只是将它们指向Mozilla Closures页面.这是我发现的关闭基础知识和实际用法的最佳,最简洁和简单的解释.强烈建议任何学习JavaScript的人.

是的,我甚至推荐给一个6岁的孩子 - 如果这个6岁的孩子正在学习闭包,那么他们已经准备好理解文章中提供的简洁和简单的解释了.


Dinesh Kaniv.. 42

我相信较短的解释,请参阅下图.

在此输入图像描述

function f1() ..>浅红色盒子

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

这里我们有两个功能,f1()f2().f2()在f1()的内部.f1()有一个变量,var x = 10.

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

这是代码:

function f1() {
    var x=10;

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

    return f2

}
f1()

f1() 在这里调用:

在此输入图像描述


Alireza.. 34

闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此.

所以基本上闭包是另一个函数的函数.我们可以说像儿童功能.

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

内部函数不仅可以访问外部函数的变量,还可以访问外部函数的参数.注意,内部函数不能调用外部函数的arguments对象,但是,即使它可以直接调用外部函数的参数.

您可以通过在另一个函数中添加函数来创建闭包.

此外,它是非常有用的方法,用于许多着名的框架,包括Angular,Node.jsjQuery:

闭包广泛用于Node.js; 他们是Node.js异步,非阻塞架构的主力军.闭包也常用于jQuery以及您阅读的每一段JavaScript代码.

但是真实编码中的闭包是怎么样的?看看这个简单的示例代码:

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开发人员都使用它很多:

$(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的一个很好的解释和示例:

实用的封闭

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

因此,您可以在通常只使用单个方法的对象的任何位置使用闭包.

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

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

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

//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;
/*css*/
body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}
<!--html><!-->
<p>Some paragraph text</p>
<h1>some heading 1 text</h1>
<h2>some heading 2 text</h2>

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

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


Charlie.. 30

对于一个六岁的孩子?

您和您的家人住在神秘的Ann Ville镇.你有一个住在隔壁的朋友,所以你打电话给他们,让他们出来玩.你拨打:

000001(jamiesHouse)

一个月之后,你和你的家人离开安维尔到下一个城镇,但你和你的朋友仍然保持联系,所以现在你必须拨打你朋友居住的城镇的区号,然后再拨打他们的'适当的号码:

001 000001(annVille.jamiesHouse)

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

01 001 000001(myOldCountry.annVille.jamiesHouse)

奇怪的是,在搬到你的新国家之后,你和你的家人恰好搬到了一个名叫Ann Ville的新城镇......你恰好和一个名叫Jamie的新人交朋友......你给他们一个呼叫...

000001(jamiesHouse)

幽灵般的...

事实上,如此诡异,你告诉杰米你的祖国关于它...你对它笑得很开心.所以有一天,你和你的家人回到旧国度假.你去老城区(Ann Ville),去参观杰米...

  • "真的吗?另一个Jamie?在Ann Ville?在你的新国家!!?"
  • "是的......我们叫他们......"

02 001 000001(myNewCountry.annVille.jamiesHouse)

意见?

更重要的是,我对现代六岁孩子的耐心有很多疑问......

  • 所以..这里的clousure是什么?jamiesHouse?我不明白 (8认同)

Shushanth Pa.. 30

在JavaScript中,闭包非常棒,其中变量或参数可用于内部函数,即使在返回外部函数之后它们也会存活.

  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


Mayur Randiv.. 28

这是一个简单的实时场景.请仔细阅读,您将了解我们如何使用闭包(请参阅座位编号如何变化).

之前解释的所有其他示例也非常适合理解该概念.

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


Abrar Jahin.. 26

闭包允许JavaScript程序员编写更好的代码.富有创意,富有表现力和简洁.我们经常在JavaScript中使用闭包,而且,无论我们的JavaScript经验如何,我们无疑会一次又一次地遇到它们.闭包可能看起来很复杂,但希望在您阅读本文后,闭包将更容易理解,因此对您日常的JavaScript编程任务更具吸引力.

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

什么是关闭?

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

内部函数不仅可以访问外部函数的变量,还可以访问外部函数的参数.注意,内部函数不能调用外部函数的arguments对象,但是,即使它可以直接调用外部函数的参数.

您可以通过在另一个函数中添加函数来创建闭包.

JavaScript中闭包的基本示例:

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闭包示例:

$(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中的函数执行时,它们使用创建它们时生效的相同范围链.这意味着即使在返回外部函数之后,内部函数仍然可以访问外部函数的变量.因此,您可以稍后在程序中调用内部函数.此示例演示:

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. Closures存储对外部函数变量的引用:

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


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循环变化时,它们也会导致错误.从而:

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



更多信息可以在这里找到 -

  1. http://javascript.info/tutorial/closures

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


Andy.. 24

这是我能给出的最禅的答案:

你期望这段代码做什么?在运行它之前在评论中告诉我.我很好奇!

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新手 - 最后忽略"未定义"),那么您已经无法理解. 换句话说,变量i是内部函数实例闭包的一部分.

我这样说是因为,一旦我了解,这个代码将实例foo()内部函数的中barbaz,然后通过这些变量调用它们,其他什么都让我感到惊讶.

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


Vitim.us.. 22

鉴于以下功能

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执行完毕,该闭包仍然存在.

在此输入图像描述

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

抽象闭包可以表示为这样的东西:

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作为一个constructor
  • local variablesinstance properties
  • 这些properties是私人的
  • inner functionsinstance methods

每次function调用a

  • object将创建一个包含所有局部变量的新变量.
  • 此对象的方法可以访问"properties"该实例对象.


roland.. 22

我越想到关闭越多,我认为这是一个两步过程:init - action

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

对于一个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).

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');

因为如果你带来了一条非常重要的信息(地图),你就足够了解其他类似的行为:

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

对于开发人员,我将在闭包和OOP之间进行并行.在初始化阶段类似于参数传递给在传统的面向对象的语言构造; 该行动阶段为最终调用来实现你想要的方法.并且该方法使用称为闭包的机制访问这些init参数.

请参阅我的另一个答案,说明OO和闭包之间的并行性:

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


Ravi.. 21

尽管互联网上存在许多关于JavaScript闭包的漂亮定义,但我试图用我最喜欢的闭包定义来解释我六岁的朋友,这有助于我更好地理解闭包.

什么是关闭?

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

闭包是函数的局部变量 - 在函数返回后保持活动状态.

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

闭包是范围概念的扩展.使用闭包,函数可以访问创建函数的作用域中可用的变量.

闭包是一个堆栈帧,在函数返回时不会释放.(好像'stack-frame'是malloc'ed而不是堆栈!)

诸如Java之类的语言提供了将方法声明为私有的能力,这意味着它们只能由同一类中的其他方法调用.JavaScript不提供本机方式,但可以使用闭包来模拟私有方法.

"闭包"是一个表达式(通常是一个函数),它可以将自由变量与绑定这些变量的环境("关闭"表达式)结合在一起.

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

闭包的用途:

闭包在隐藏功能实现的同时仍然可以显示界面.

您可以使用闭包在JavaScript中模拟封装概念.

闭包在jQueryNode.js中广泛使用.

虽然对象文字很容易创建并且便于存储数据,但是在大型Web应用程序中创建静态单例命名空间时,闭包通常是更好的选择.

闭包示例:

假设我6岁的朋友最近在他的小学开始了解补充,我觉得这个添加两个数字的例子对于六岁的学生来说是最简单和最容易学习的.

示例1:通过返回函数来实现闭包.

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:通过返回对象文字来实现闭包.

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中的闭包

$(function(){
    var name="Closure is easy";
    $('div').click(function(){
        $('p').text(name);
    });
});

有用的链接:

感谢以上链接,这有助于我更好地理解和解释关闭.


enb081.. 21

闭包是函数中的一个函数,可以访问其"父"函数的变量和参数.

例:

function showPostCard(Sender, Receiver) {

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

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

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


Dmitry Frank.. 21

满足图解说明:JavaScript闭包如何在幕后工作.

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

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

执行顶级代码时,我们有以下范围对象的排列:

在此输入图像描述

myFunc()被调用时,我们有以下范围链:

在此输入图像描述

了解范围对象的创建,使用和删除方式是了解大局并了解封闭装置如何工作的关键.

有关所有细节,请参阅上述文章.


Nikhil Ranja.. 21

要理解闭包,你必须开始执行程序并逐字执行,就好像你是运行时一样.我们来看看这段简单的代码:

在此输入图像描述

JavaScript分两个阶段运行代码:

  • 编译阶段// JavaScript不是纯粹的解释语言
  • 执行阶段

当JavaScript经历编译阶段时,它会提取变量和函数的声明.这称为吊装.在此阶段遇到的函数将保存为内存中的文本blob,也称为lambda.编译完成后,JavaScript进入执行阶段,分配所有值并运行该函数.要运行该函数,它通过从堆中分配内存并重复该函数的编译和执行阶段来准备执行上下文.该存储区域称为功能范围.执行开始时有一个全局范围.范围是理解闭包的关键.

在此示例中,首先a定义变量,然后f在编译阶段定义变量.所有未声明的变量都保存在全局范围内.在执行阶段f使用参数调用.f分配了范围,并为其重复编译和执行阶段.

参数也保存在此本地范围中f.每当创建本地执行上下文或作用域时,它都包含指向其父作用域的引用指针.所有变量访问都遵循此词法范围链来查找其值.如果在本地范围内找不到变量,则它跟随链并在其父范围内找到它.这也是局部变量覆盖父作用域中变量的原因.父作用域称为本地范围或函数的"闭包".

g设置范围时,它有一个指向其父项范围的词法指针f.范围f是关闭g.在JavaScript中,如果有一些函数,对象或作用域的引用,如果你能以某种方式访问​​它们,它将不会被垃圾收集.所以当myG运行时,它有一个指向其范围的指针f就是它的闭包.即使f已经返回,这个内存区也不会被垃圾收集.就运行时而言,这是一个闭包.

什么是关闭?

  • 它是函数与其范围链之间的隐式永久链接......
  • 函数定义的(lambda)隐藏[[scope]]引用.
  • 保留范围链(防止垃圾收集).
  • 无论何时运行该函数,它都被用作"外部环境引用".

隐含的封闭

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

显性关闭

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 internals,EventLoop,Async和ScopeChains.


Rafael Eyng.. 20

(我不会考虑6岁的事情.)

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

var name = 'Rafael';

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

你看,sayName没有name变量的定义,但确实使用了namesayName(在父范围内)定义的值.

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

functionThatTakesACallback(sayName);

注意:

  1. sayName将从内部调用functionThatTakesACallback(假设,因为我functionThatTakesACallback在这个例子中没有实现).
  2. sayName被调用时,它会记录下的数值name变量.
  3. functionThatTakesACallback没有定义name变量(好吧,它可以,但它没关系,所以假设它没有).

因此我们sayName在内部调用functionThatTakesACallback并引用name未在内部定义的变量functionThatTakesACallback.

那么会发生什么?一个ReferenceError: name is not defined

没有!值的值name闭包内捕获.您可以将此闭包视为与函数关联的上下文,该函数包含定义该函数的可用值.

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

-

Eloquent JavaScript这本书中:

一个好的心理模型是将函数值视为包含其体内的代码和创建它们的环境.调用时,函数体会看到其原始环境,而不是进行调用的环境.


christian Ng.. 19

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

忘记范围每一件事并记住:当变量需要某处时,javascript不会破坏它.变量始终指向最新值.

例1:

在此输入图像描述

例2:

在此输入图像描述

例3: 在此输入图像描述


Mohammed Saf.. 18

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

function outer(x){
     return function inner(y){
         return x+y;
     }
}

像这样调用函数:

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


Shivprasad K.. 18

这个答案是这个youtube视频Javascript Closures的摘要.这个视频的完全信用.

闭包只是状态函数,它们维护私有变量的状态.

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

在此输入图像描述

但是现在有些情况下我们想要保持Javascript闭包使用的函数的这种状态.闭包是函数内部的函数,具有返回调用,如下面的代码所示.

在此输入图像描述

所以上面的计数器函数的闭包代码看起来如下所示.它是一个带有return语句的函数内部函数.

function Counter() {
           var counter = 0;

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

所以现在如果你进行调用,计数器将递增,换句话说,函数调用维持状态.

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

但现在最大的问题是使用这种有状态功能.有状态函数是实现OOP概念的构建块,如抽象,封装和创建自包含模块.

因此,无论你想要封装什么,你都可以将它作为私有,并且应该将公开的内容放在return语句中.这些组件也是自包含的隔离对象,因此它们不会污染全局变量.

遵循OOP原则的对象是自包含的,遵循抽象,遵循封装等.在Javascript中使用out闭包这很难实现.

在此输入图像描述


Harry Robbin.. 17

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

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

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吗?感谢强大的关闭.

我为什么要在乎?

你真的不必知道闭包使用它们.但正如其他人也指出,它们可以被利用来创造人造私有变量.在您需要私有变量之前,只需像往常一样使用它们.


Jérôme Verst.. 16

来自个人博客文章:

默认情况下,JavaScript知道两种类型的范围:全局和本地.

var a = 1;

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

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

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

function a(x) {
    return function b(y) {
        return x + y;
    }
}

这里,函数a返回一个名为的函数b.由于b被内定义a,它会自动访问任何定义中a,也就是,x在这个例子中.这就是为什么b可以返回x+ y而不声明x.

var c = a(3);

变量c被赋予调用带参数3的结果.也就是说,函数的实例bwhere x= 3.换句话说,c现在是一个等效于的函数:

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

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

var d = c(4);

将值3 + 4分配给d,即7.

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

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

(function () {
    var f = "Some message";
    alert(f);
})();

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

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

var a = new Array();

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

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

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

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

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

解决方案是:

var a = new Array();

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

参数/变量tmp包含i创建函数实例时更改值的本地副本.


大智慧.. 16

我发现非常清楚第8章第6节"闭包"的JavaScript:大卫弗拉纳根的权威指南,第6版,O'Reilly,2011.我会试着解释一下.

  1. 调用函数时,会创建一个新对象来保存该调用的局部变量.

  2. 函数的作用域取决于其声明位置,而不是其执行位置.

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

JavaScript将保留这些值,因为它们已经在完成的外部函数中传递,因此它们仍然保留在当前执行的范围内.所有函数都是闭包,但感兴趣的闭包是内部函数,在我们假设的场景中,当它们(内部函数)被返回时,它们的"封闭"内部保留外部函数值(我希望我在这里正确使用语言)来自外部功能.我知道这不符合六年的要求,但希望它仍然有用.


Arman McHita.. 14

我敢肯定,爱因斯坦没有直截了当地期待我们选择任何深奥的头脑风暴的东西,而且只是徒劳地试图让那些"疯狂"的东西超过六岁的孩子(对他们来说更糟糕的是 - 无聊对他们幼稚的思想的事情:)如果我六岁,我不想有这样的父母或不会与这些无聊的慈善家建立友谊,抱歉:)

无论如何,对于婴儿来说,关闭只是一个拥抱,我猜,无论你怎么解释:)当你拥抱你的朋友时,你们两个人分享你们现在拥有的任何东西.这是一个通过仪式,一旦你拥抱了某个人,你就会表现出她的信任,并愿意让她与你做很多你不允许的事情并且会躲避别人.这是友谊的行为:).

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

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

仅限儿童:

关闭拥抱

错误

KISSsmooch!:)


Jean-Baptist.. 13

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

告诉孩子他是"开放的",这表明他能够与其他人,他的朋友建立关系.在某个时间点,他确定了朋友(我们可以知道他朋友的名字),这是一个封闭.如果你拍摄他和他的朋友的照片,那么他相对于他的友谊能力是"封闭的".但总的来说,他是"开放的".在他的一生中,他会有许多不同的朋友.其中一组是关闭.


Juan Garcia.. 13

如果你理解得很好,你可以解释它很简单.最简单的方法是从上下文中抽象出来.除了代码,甚至编程放在一边.一个比喻的例子会做得更好.

让我们想象一个功能是一个墙壁是玻璃的房间,但它们是特殊的玻璃,就像在审讯室里一样.从外面看,它们是不透明的,从内部透明.它可以是其他房间内的房间,唯一的联系方式是电话.

如果你从外面打电话,你不知道里面有什么,但你知道如果你给他们一定的信息,里面的人会做任务.他们可以看到外面的东西,所以他们可以向你询问外面的东西并对那些东西做出改变,但你不能从外面改变它的内部,你甚至看不到(知道)里面的内容.你打电话给那个房间的人看到外面的东西,但不知道房间内的房间是什么,所以他们就像你从外面那样与他们互动.最内部房间内的人可以看到很多东西,但最外层房间的人甚至不知道最内部房间的存在.

对于每个内室的呼叫,该房间的人员记录有关该特定呼叫的信息,并且他们做得非常好,以至于他们从不将一个呼叫内容与其他呼叫内容混淆.

房间是功能,可见性是范围,人做任务是陈述,东西是对象,电话是函数调用,电话信息是参数,呼叫记录是范围实例,最外层的房间是全局对象.


b_dev.. 13

想象一下,在你的城镇里有一个非常大的公园,你可以看到一个名叫科德先生的魔术师用他的魔杖(称为JavaScript)在公园的不同角落开始棒球比赛.

当然,每个棒球比赛都有完全相同的规则,每个比赛都有自己的比分板.

当然,一个棒球比赛的得分与其他比赛完全分开.

关闭是特别的方式,科德先生将他所有神奇的棒球比赛的得分分开.


Ron Deijkers.. 13

Pinocchio:1883年的封闭(在JavaScript之前的一个世纪)

我认为这可以最好地解释给一个6岁的孩子带着一个不错的冒险...... Pinocchio冒险的一部分Pinocchio被一只超大的鲨鱼吞噬了......

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

 


Premraj.. 13

闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此.

var add = (function() {
  var counter = 0;
  return function() {
    return counter += 1;
  }
})();

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

示例说明:

  • 为变量add分配自调用函数的返回值.
  • 自调用函数只运行一次.它将计数器设置为零(0),并返回一个函数表达式.
  • 这样添加成为一种功能."精彩"部分是它可以访问父范围中的计数器.
  • 这称为JavaScript闭包.它使函数具有"私有"变量成为可能.
  • 计数器受匿名函数范围的保护,只能使用add函数进行更改.

资源


大智慧.. 12

闭包是一种方法,在父函数已经终止之后,内部函数可以通过这些函数引用外部封闭函数中存在的变量.

// 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 );


moha297.. 12

函数在定义它的对象/函数的范围内执行.所述函数可以访问在执行时定义的对象/函数中定义的变量.

从字面上看它......就像代码写的那样:P


Magne.. 12

闭包是一个符合三个标准的代码块:

  • 它可以作为值传递

  • 具有该值的任何人按需执行

  • 它可以从创建它的上下文中引用变量(也就是说,它是关于变量访问的,在"闭合"这个词的数学意义上是关闭的).

("闭包"这个词实际上有一个不精确的含义,有些人不认为标准#1是定义的一部分.我认为它是.)

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

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


Psy Chip.. 12

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

var calculate = {
    number: 0,
    init: function (num) {
        this.number = num;
    },
    add: function (val) {
        this.number += val;
    },
    rem: function (val) {
        this.number -= val;
    }
};

并从calculate.number变量中读取结果,无论如何都需要"返回".


goonerify.. 11

调用函数后,它将超出范围.如果该函数包含类似回调函数的内容,则该回调函数仍在范围内.如果回调函数在父函数的直接环境中引用了某个局部变量,那么你自然会希望该变量对回调函数是不可访问的并返回undefined.

闭包确保回调函数引用的任何属性都可供该函数使用,即使其父函数可能已超出范围.


Bhojendra Ra.. 11

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

例:

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"


Eugene Tiuri.. 11

闭包很简单

你可能不应该告诉六岁的闭包,但是如果你这样做,你可能会说闭包能够获得对在其他函数范围内声明的变量的访问权限.

在此输入图像描述

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 :|')
});
<pre id="output"></pre>

<script>
  function out(k) {
    document.getElementById('output').innerHTML += '> ' + k + '\n';
  }
</script>


Raul Martins.. 10

考虑到问题是关于解释它就好像是一个6岁的人,我的答案是:

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

  • 没有必要成为之前的行,你可以在函数声明之后声明函数的同一作用域中声明一个var或函数,它也会看到它.JavaScript它不是行解释器. (2认同)

soundyogi.. 10

不包含自由变量的函数称为纯函数.

包含一个或多个自由变量的函数称为闭包.

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


Durgesh Pand.. 10

闭包是一种稍微高级且经常被误解的JavaScript语言特性.简而言之,闭包是包含函数的对象,以及对创建函数的环境的引用.但是,为了完全理解闭包,必须首先理解JavaScript语言的另外两个特性 - 第一类函数和内部函数.

一流的功能

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

var foo = function() {
  alert("Hello World!");
};

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

bar(foo)();

内在功能

内部函数(也称为嵌套函数)是在另一个函数内部定义的函数(称为外部函数).每次调用外部函数时,都会创建内部函数的实例.以下示例显示了如何使用内部函数.在这种情况下,add()是外部函数.在add()内部,定义并调用doAdd()内部函数.

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

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

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

创建闭包

当内部函数可以从创建它的函数外部访问时,创建一个闭包.这通常在外部函数返回内部函数时发生.发生这种情况时,内部函数会维护对创建它的环境的引用.这意味着它会记住当时在范围内的所有变量(及其值).以下示例显示了如何创建和使用闭包.

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

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

关于这个例子,有很多事情需要注意.

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

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

何时使用闭包

闭包可用于完成许多事情.它们对于配置带参数的回调函数非常有用.本节介绍两种情况,即闭包可以使您作为开发人员的生活变得更加简单.

与计时器合作

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

<!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代码以使用闭包.

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类的构造函数.创建每个Person时,将通过" name "参数为其指定名称.在内部,Person将其名称存储在" _name "变量中.遵循良好的面向对象编程实践,还提供了getName()方法来检索名称.

function Person(name) {
  this._name = name;

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

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

var person = new Person("Colin");

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

就个人而言,如果只有任何人能够合法地改变我的名字,我不会喜欢它.为了阻止这种情况发生,可以使用闭包来使"_name"变量变为私有.使用闭包在下面重写了Person构造函数.请注意,"_ name"现在是Person构造函数的局部变量,而不是对象属性.形成一个闭包是因为外部函数Person()通过创建public getName()方法来公开内部函数.

function Person(name) {
  var _name = name;

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

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

var person = new Person("Colin");

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

何时不使用闭包

了解闭包的工作原理以及何时使用它们非常重要.同样重要的是要了解它们何时不适合手头的工作.过度使用闭包会导致脚本执行缓慢并消耗不必要的内存.而且因为闭包很容易创建,所以有可能在不知道它的情况下滥用它们.本节介绍了应谨慎使用闭包的几种情况.

在循环中

在循环内创建闭包可能会产生误导性结果.这方面的一个例子如下所示.在此示例中,创建了三个按钮.单击"button1"时,应显示"单击按钮1"的警报.应为"button2"和"button3"显示类似的消息.但是,运行此代码时,所有按钮都显示"单击按钮4".这是因为,当单击其中一个按钮时,循环已完成执行,并且循环变量已达到其最终值4.

<!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"循环的闭包.

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

在构造函数中不必要的使用

构造函数是关闭滥用的另一个常见原因.我们已经看到了如何使用闭包来模拟私有数据.但是,如果实际上不访问私有数据,那么将方法实现为闭包就太过分了.下面的示例重新访问Person类,但这次添加了一个不使用私有数据的sayHello()方法.

function Person(name) {
  var _name = name;

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

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

每次实例化Person时,都会花费时间创建sayHello()方法.如果创建了许多Person对象,则会浪费时间.更好的方法是将sayHello()添加到Person原型中.通过添加到原型,所有Person对象可以共享相同的方法.这样就不必为每个实例创建一个闭包,从而节省了构造函数的时间.前面的示例在下面重写,外部闭包移入原型.

function Person(name) {
  var _name = name;

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

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

要记住的事情

  • 闭包包含一个函数和对创建函数的环境的引用.
  • 当外部函数暴露内部函数时形成闭包.闭包可用于轻松地将参数传递给回调函数.
  • 可以使用闭包来模拟私有数据.这在面向对象的编程和命名空间设计中很常见.
  • 闭包不应该在构造函数中过度使用.添加到原型是一个更好的主意.

链接


TastyCode.. 9

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

闭包是指函数能够记住并访问其词法范围,即使该函数在其词法范围之外执行也是如此.

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

以下是他在"你不了解JS:范围和闭包"系列丛书中提供的修改示例.

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


Brandon Kent.. 9

MDN解释说我认为最好:

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

闭包始终具有外部功能和内部功能.内部函数是所有工作发生的地方,外部函数只是保留内部函数创建范围的环境.通过这种方式,闭包的内部函数"记住"创建它的环境/范围.最经典的例子是计数器功能:

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(),内部函数(工作函数)都可以递增它.


Pawel Furman.. 8

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

在JavaScript中,它发生在您:

  • 在另一个函数中定义一个函数
  • 返回外部函数后调用内部函数
// '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);
}


ejectamenta.. 8

对于一个六岁的孩子......

你知道什么是对象吗?

对象是具有属性和做东西的东西.

关闭闭包最重要的一点是它们允许你用JavaScript创建对象.JavaScript中的对象只是函数和闭包,它允许JavaScript在创建对象后存储对象的属性值.

对象非常有用并且保持一切美观和有条理.不同的对象可以做不同的工作,一起工作对象可以做复杂的事情.

很幸运,JavaScript已经关闭了制作对象,否则一切都会成为一个混乱的噩梦.


大智慧.. 8

这就是一个初学者如何将一个人的头部缠绕在Closures上,就像一个函数被包裹在一个函数体(也称为Closures)中.

Speaking JavaScript一书中的定义"闭包是一个函数加上与函数创建范围的连接" - Dr.Axel Rauschmayer

那看起来怎么样?这是一个例子

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关闭增量,计数器可以通过增量引用和访问.

counter1counter2将跟踪自己的价值.

简单但有希望清楚地了解封闭是围绕所有这些伟大和先进的答案.


David Rosson.. 7

最好的方法是逐步解释这些概念:

变量

console.log(x);
// undefined

在这里,undefinedJavaScript的方式是"我不知道是什么x意思."

变量就像标签.

你可以说,标记x点值42:

var x = 42;
console.log(x);
// 42

现在JavaScript知道什么x意思.

您还可以重新分配变量.

使标记x指向不同的值:

x = 43;
console.log(x);
// 43

现在x意味着别的东西.

范围

当你创建一个函数时,该函数有自己的变量"框".

function A() {
  var x = 42;
}

console.log(x);

// undefined

从盒子外面,你看不到盒子里面的东西.

但是从盒子里面,你可以看到盒子外面的东西:

var x = 42;

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

// 42

在内部功能中A,您具有"范围访问权限" x.

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

function A() {
  var x = 42;
}

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

// undefined

在函数内部B,您无法访问函数内部的变量A.

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

function A() {

  var x = 42;

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

}

// 42

您现在拥有"范围访问权限".

功能

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

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

像这样:

A();

// 42

作为值的功能

在JavaScript中,您可以将标记指向函数,就像指向数字一样:

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

变量a现在意味着一个函数,你可以运行它.

a();
// 42

你也可以传递这个变量:

setTimeout(a, 1000);

在第二个(1000毫秒)中,函数a指向:

// 42

关闭范围

现在,当您定义函数时,这些函数可以访问其外部作用域.

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

在JavaScript中,函数保持对外部范围变量的访问.即使他们被传递到其他地方也能运行.

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

}

现在发生了什么?

// 'Hello!'

或者考虑一下:

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

您仍然可以在闭包范围中访问变量.

即使a已经完成运行,现在你正在c外面跑a.

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


NinjaBeetle.. 7

曾经有一个穴居人

function caveman {

谁有一个非常特别的岩石,

var rock = "diamond";

你自己无法获得岩石,因为它是在穴居人的私人洞穴里.只有穴居人知道如何找到并获得岩石.

return {
    getRock: function() {
        return rock;
    }
};
}

幸运的是,他是一个友好的穴居人,如果你愿意等他回来,他会乐意为你服务.

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

相当聪明的穴居人.


Alexis.. 7

关闭并不难理解.这仅取决于观点.

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

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

我们只需要在特定情况下执行某些步骤.至于材料的转换只有在你有零件时才有用.


Taye.. 6

闭包基本上创建了两件事: - 一个函数 - 只有该函数才能访问的私有作用域

这就像在功能周围涂上一些涂层.

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

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

告诉我他看过多少只鸟是返回的功能.我也创造了那个.

这个比喻有点太糟糕了,但是有人可能会改进它.


cube.. 6

关闭一词只是指能够在一个功能(六岁:盒子)中访问已关闭(六岁:私人)的对象(六岁:事物).即使功能(六岁:盒子)超出范围(六岁:发送很远).


williambq.. 6

我以前读过所有这些,但它们都非常有用.有些人非常接近得到简单的解释然后变得复杂或保持抽象,打败目的并且未能显示非常简单的现实世界用途.

虽然通过所有的例子和解释,你可以很好地了解闭包是什么,而不是通过评论和代码,我仍然不满意一个非常简单的插图,帮助我获得闭包有用而不会变得如此复杂.我的妻子想要学习编码,我想我不仅要在这里展示什么,而且为什么,以及如何展示.

我不确定一个六岁的孩子会得到这个,但我认为这可能会更接近于以现实世界的方式展示一个简单的案例,这个案例可能非常有用并且很容易理解.

其中一个最好的(或最接近最简单的)是莫里斯的"傻瓜闭馆"的复述.

采用"SayHi2Bob"概念只需要进一步展示您可以从阅读所有答案中收集到的两个基本内容:

  1. 闭包可以访问包含函数的变量.
  2. 闭包持久存在于自己的内存空间中(因此对于所有类型的oop-y实例化内容都很有用)

向我自己证明和展示这一点,我做了一点小提琴:

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

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实践,单例与具有自己数据的实例化实例等的基本概念等.

如果你用这个开始一个新手,那么你可以继续进行更复杂的基于对象属性/成员的调用,并希望这些概念可以随之进行.


nomen.. 6

我认为退后一步并检查一个更为笼统的"闭合"概念 - 即所谓的"连接算子"是有价值的.

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

因此,连接为您提供了比部件"更大"的最小的东西.

现在,请注意JavaScript范围是部分有序的结构.因此,有一个合情合理的概念.特别是,范围的连接是比原始范围更大的最小范围.该范围称为闭包.

因此,变量a,b,c的闭包是将a,b和c带入范围的最小范围(在程序范围的格子中!).


大智慧.. 6

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

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

    }
})();

另一方面,如果您想在闭包外部显示一个或多个变量或方法,则可以将它们返回到对象文字中.像这样:

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

希望能帮助到你.问候,


Alireza.. 6

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

词汇范围
考虑以下内容:

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()函数没有自己的局部变量.但是,因为内部函数可以访问外部函数的变量,所以displayName()可以访问父函数init()中声明的变量名.

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变量的值,该值在其父函数中声明.这是词法作用域的一个示例,它描述了解析器在嵌套函数时如何解析变量名称."lexical"一词指的是词法范围使用在源代码中声明变量的位置来确定变量可用的位置.嵌套函数可以访问在其外部作用域中声明的变量.

关闭
现在考虑以下示例:

function makeFunc() {
  var name = 'Mozilla';
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

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

乍一看,这段代码仍然有效,这似乎不直观.在某些编程语言中,函数中的局部变量仅在该函数执行期间存在.一旦makeFunc()完成执行,您可能会期望名称变量不再可访问.但是,因为代码仍然按预期工作,所以在JavaScript中显然不是这种情况.

原因是JavaScript中的函数形成闭包.闭包是函数和声明该函数的词法环境的组合.此环境由创建闭包时在范围内的任何局部变量组成.在这种情况下,myFunc是对运行makeFunc时创建的函数displayName的实例的引用.displayName的实例维护对其词汇环境的引用,其中存在变量名称.因此,当调用myFunc时,变量名仍可供使用,"Mozilla"将传递给alert.

这是一个稍微有趣的例子 - 一个makeAdder函数:

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.

实用的封闭

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

因此,您可以在通常只使用单个方法的对象的任何位置使用闭包.

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

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

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

我们的交互式文本大小按钮可以更改body元素的font-size属性,并且由于相对单位,调整将由页面上的其他元素拾取.这是JavaScript:

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像素的函数.我们可以将它们附加到按钮(在本例中为链接),如下所示:

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

<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>


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上链接


大智慧.. 5

我对闭包的看法:

可以将闭包与书架上的书籍进行比较.

假设您已经阅读了一本书,并且您喜欢本书中的某些页面.您在该页面上放置了一个书签来跟踪它.

现在,一旦你读完这本书,你就不再需要这本书了,除了你想要访问那个页面.你可能只是剪掉了页面,但是你会松开故事的背景.因此,您将书籍放回书架中,并带有书签.

这与封闭类似.这本书是外部函数,页面是你的内部函数,它从外部函数返回.书签是对页面的引用,故事的上下文是词法范围,您需要保留它.书架是功能堆栈,不能清理旧书,直到你抓住页面.

代码示例:

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()返回对闭包的引用,即不仅是函数,而且是对书及其上下文的引用,即对函数getPage,页面状态和bookMarkedPage变量的引用.

有些要考虑的要点:

第1点: 书架,就像功能堆栈的空间有限一样,所以明智地使用它.

要点2: 想一想这样一个事实,即当您只想跟踪单个页面时是否需要保留整本书.您可以通过在返回闭包时不存储书中的所有页面来释放部分内存.

这是我对闭包的看法.希望它有所帮助,如果有人认为这是不正确的,请告诉我,因为我非常有兴趣了解范围和封闭更多!


归档时间:

查看次数:

1317037 次

最近记录:

9 月,2 周 前