7615

如果有人知道JavaScript闭包所包含的概念(例如函数、变量等),但又不了解闭包本身,你会如何向他解释呢?

我已经看到了Scheme示例在维基百科上发布,但不幸的是它没有帮助。

0

86个答案86

重置为默认值
8347
+100

闭包是一对:

  1. A函数和
  2. 对该函数外部范围(词汇环境)的引用

词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名)和值之间的映射。

JavaScript中的每个函数都维护对其外部词汇环境的引用。此引用用于配置调用函数时创建的执行上下文。此引用使函数内的代码能够“查看”函数外声明的变量,而不管何时何地调用函数。

如果一个函数被一个函数调用,而该函数又被另一个函数所调用,那么就会创建一个外部词汇环境的引用链。这个链称为范围链。

在以下代码中,内部的使用在以下情况下创建的执行上下文的词汇环境形成闭包foo公司被调用,关闭变量秘密:

函数foo(){const secret=数学.trunc(Math.random()*100)返回函数inner(){console.log(`机密编号为${secret}。`)}}const f=foo()//`secret`不能从外部直接访问`foo`f()//检索“secret”的唯一方法是调用“f”`

换句话说:在JavaScript中,函数携带对私有“状态框”的引用,只有它们(以及在同一词汇环境中声明的任何其他函数)才有权访问它。此状态框对函数的调用方是不可见的,为数据隐藏和封装提供了一种极好的机制。

记住:JavaScript中的函数可以像变量(一级函数)一样传递,这意味着这些功能和状态的配对可以在程序中传递,就像在C++中传递类的实例一样。

如果JavaScript没有闭包,那么函数之间必须传递更多状态明确地,使参数列表更长,代码噪音更大。

因此,如果您希望函数始终可以访问私有状态块,可以使用闭包。

……我们经常希望将状态与函数关联。例如,在Java或C++中,当您向类添加私有实例变量和方法时,您将状态与功能相关联。

在C语言和大多数其他通用语言中,函数返回后,所有局部变量都将无法访问,因为堆栈框架已被破坏。在JavaScript中,如果在另一个函数中声明一个函数,那么外部函数的局部变量在返回后仍然可以访问。这样,在上面的代码中,秘密对函数对象仍然可用内部的,之后它已经从foo公司.

封口的使用

每当您需要与函数关联的私有状态时,闭包都很有用。这是一个非常常见的场景——记住:JavaScript直到2015年才有类语法,而且它仍然没有私有字段语法。关闭满足了这一需要。

私有实例变量

在以下代码中,函数toString(字符串)关闭了汽车的细节。

功能车(制造商、型号、年份、颜色){返回{toString(){返回`${manufacturer}${model}(${year},${color})`}}}const car=新车('Aston Martin'、'V8 Vantage'、'2012年'、'Quantum Silver')console.log(car.toString())

函数式编程

在以下代码中,函数内部的关闭两个fn公司参数.

函数curry(fn){常量参数=[]内部返回函数(arg){如果(args.length===fn.length)返回fn(…args)参数推送(arg)返回内部}}函数加法(a,b){返回a+b}const curriedAdd=咖喱(添加)console.log(curriedAdd(2)(3)())//5

面向事件的编程

在以下代码中,函数onClick(单击)关闭变量背景_颜色.

const$=document.querySelector.bind(document)const BACKGROUND_COLOR=“rgba(200,200,242,1)”onClick()函数{$('body').style.background=背景色}$(“按钮”).addEventListener(“单击”,onClick)
<button>设置背景颜色</button>

模块化

在下面的示例中,所有实现细节都隐藏在立即执行的函数表达式中。功能打上钩toString(字符串)关闭私有状态和完成工作所需的函数。闭包使我们能够模块化和封装代码。

让名称空间={};(函数foo(n){让数字=[]函数格式(n){return数学trunc(n)}函数tick(){numbers.push(数学.random()*100)}函数toString(){return numbers.map(格式)}n.计数器={勾选,toString(字符串)}}(命名空间))常量计数器=命名空间计数器计数器刻度()计数器刻度()console.log(counter.toString())

示例

示例1

这个例子表明局部变量没有在闭包中复制:闭包保持对原始变量的引用他们自己。这就好像堆栈帧在内存中保持活动状态,即使在外部函数退出后也是如此。

函数foo(){设x=42let内部=()=>console.log(x)x=x+1返回内部}foo()()//日志43

示例2

在以下代码中,有三种方法日志,增量、和更新都是在相同的词汇环境中。

而且每次创建对象调用时,将创建一个新的执行上下文(堆栈帧)和一个全新的变量x个,以及一组新功能(日志等),从而关闭此新变量。

函数createObject(){设x=42;返回{log(){console.log(x)},increment(){x++},更新(值){x=value}}}const o=创建对象()o.增量()o.log()//43o.update(更新)(5)o.log()//5常量p=创建对象()p.log()//42页

示例3

如果使用的变量声明为无功功率,无功功率,请注意您要了解要关闭的变量。使用声明的变量无功功率,无功功率被吊起。由于引入了常数.

在下面的代码中,每次循环时都会有一个新函数内部的创建,关闭.但是因为变量i被提升到循环之外,所有这些内部函数都靠近同一个变量,这意味着(3) 打印了三次。

函数foo(){var结果=[]对于(var i=0;i<3;i++){push(函数inner(){console.log(i)})}返回结果}常量结果=foo()//以下内容将打印“3”三次。。。对于(var i=0;i<3;i++){结果[i]()}

最后一点:

  • 每当在JavaScript中声明函数时,都会创建闭包。
  • 返回功能从内部来看,另一个函数是closure的经典示例,因为即使在外部函数完成执行之后,外部函数内部的状态也可以隐式地供返回的内部函数使用。
  • 无论何时使用评估()在函数内部使用闭包。你的文字评估可以引用函数的局部变量,在非限定模式下,甚至可以使用eval('var foo=…').
  • 当您使用新建函数(…)(该函数构造函数)在函数内部,它不会在其词汇环境中关闭:而是在全局上下文中关闭。新函数不能引用外部函数的局部变量。
  • JavaScript中的闭包类似于保留引用(不是一个副本)到函数声明点的作用域,它反过来保持对其外部作用域的引用,以此类推,一直到作用域链顶部的全局对象。
  • 声明函数时创建闭包;此闭包用于在调用函数时配置执行上下文。
  • 每次调用函数时都会创建一组新的局部变量。

链接

  • 11
    我只做了六年的前端开发人员,所以我很好奇常见的例子咖喱添加(2)(3)()在解释闭包或编码访谈时,除了函数编程示例之外。我做过很多代码审查,但从来没有遇到过,但我也从来没有像我想象的那样与计算机科学MVP合作过。 评论 2022年3月14日14:26
  • 毫无疑问,你写的是真的,尽管我还没有亲自核实。然而,我觉得这很令人不安。你能做一个编辑来演示如何打印0、1和2吗。另外,在两个循环中,i都小于3,那么它怎么可能打印3呢?它是否经过并在循环结束后执行。注意:在我学习javascirpt之前,我先是从“C”开始,然后是C++,最后是C#背景。 评论 2023年4月20日14:53
  • 这需要更新,因为JS现在确实提供了带有#前缀的私有字段/方法语法。 评论 2月8日13:14
4145

JavaScript中的每个函数都保持与外部词汇环境的链接。词汇环境是一个范围内所有名称(例如变量、参数)及其值的映射。

所以,每当你看到功能关键字,则该函数内的代码可以访问函数外声明的变量。

函数foo(x){var tmp=3;功能栏(y){控制台.log(x+y+(++tmp));//将记录16}巴(10);}foo(2);

这将记录16因为函数酒吧关闭参数x个和变量临时管理计划它们都存在于外部功能的词汇环境中foo公司.

功能酒吧以及它与功能的词汇环境的联系foo公司是一个闭包。

函数不必返回以创建闭包。仅仅凭借其声明,每个函数都会在其封闭的词汇环境上关闭,从而形成一个闭包。

函数foo(x){var tmp=3;返回函数(y){控制台.log(x+y+(++tmp));//也将记录16}}var bar=foo(2);巴(10);//16巴(10);//17

上面的函数也会记录16,因为里面的代码酒吧仍然可以引用参数x个和变量临时管理计划即使它们不再直接在范围内。

然而,由于临时管理计划还在里面徘徊酒吧的闭包,它可以递增。每次调用时它都会递增酒吧.

闭包的最简单示例如下:

变量a=10;功能测试(){控制台.log(a);//将输出10console.log(b);//将输出6}var b=6;测试();

当调用JavaScript函数时,新的执行上下文电子商务已创建。与函数参数和目标对象一起,此执行上下文还接收到调用执行上下文的词汇环境的链接,这意味着在外部词汇环境中声明的变量(在上面的示例中,两者都是b条)可从以下位置获得电子商务.

每个函数都会创建一个闭包,因为每个函数都有一个到其外部词汇环境的链接。

注意变量他们自己从闭包内可见,副本。

0
2615

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

就像老阿尔伯特说的那样:“如果你不能向一个六岁的孩子解释,你就真的不懂。”我试着向一个27岁的朋友解释JS闭包,但完全失败了。

有人会认为我今年6岁,对这个话题很感兴趣吗?

我很确定我是唯一一个试图从字面上理解最初问题的人。从那时起,这个问题已经改变了好几次,所以我的答案现在可能看起来非常愚蠢和不合时宜。希望故事的大意对一些人来说仍然有趣。


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

从前:

有一位公主。。。

函数princes(){

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

var冒险=[];函数princeCharming(){/*…*/}var unicorn={/*…*/},龙=[/*…*/],松鼠=“你好!”;/* ... */

但她总是要回到她那充满家务和成年人的乏味世界。

返回{

她经常告诉他们她作为公主的最新惊险经历。

故事:函数(){返回冒险[冒险.长度-1];}};}

但他们只会看到一个小女孩。。。

var littleGirl=公主();

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

littleGirl.story();

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

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

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

4
  • 390
    我真的很喜欢这个解释。对于那些读过它而没有读过它的人来说,类比是这样的:princes()函数是一个包含私有数据的复杂作用域。在函数外部,无法查看或访问私有数据。公主将独角兽、龙、冒险等保存在她的想象中(私人数据),大人们自己看不到它们。但是公主的想象力被捕捉到了故事()函数,这是小女孩实例暴露在魔法世界中。 评论 2013年2月28日7:49
  • 4
    具有未定义的值会使其更难理解。这是真实的故事jsfiddle.net/rjdx34k0/3 评论 2020年9月2日19:13
  • 白马王子可以增加她的冒险经历,可以杀死所有的龙,从而将她从如下危险中解救出来:function princeCharming{adventures.push(“蜜月之旅”、“跳伞”、“访问索马里”);const pickADragonToKill=dragons.pop();} 评论 2021年1月13日5:15
  • 1
    我理解的一个关键点是添加console.log(小女孩)@Hugolpz回答。当我用devtools在littleGirl对象中钻孔时,我在任何地方都找不到princeCharming、独角兽、龙或松鼠。 评论 2022年12月19日9:42
814

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

打开儿童发展:5至7岁上面写着:

您的孩子将能够遵循两步指导。例如,如果你对你的孩子说,“去厨房给我拿个垃圾袋”,他们就会记住这个方向。

我们可以使用此示例解释闭包,如下所示:

厨房是一个包含局部变量的闭包,称为垃圾袋。厨房里有一个功能叫做获取垃圾袋他拿了一个垃圾袋并把它还了。

我们可以用JavaScript这样编码:

函数makeKitchen(){var垃圾袋=['A','B','C'];//开始只有3个返回{获取垃圾袋:函数(){return trashBags.pop();}};}var厨房=makeKitchen();console.log(kitchen.getTrashBag());//返回垃圾袋Cconsole.log(kitchen.getTrashBag());//返回垃圾袋Bconsole.log(kitchen.getTrashBag());//返回垃圾袋A

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

  • 每次制作厨房()则创建一个新的闭包,并使用它自己的垃圾袋.
  • 这个垃圾袋变量是每个厨房内部的局部变量,在外部无法访问,但在获取垃圾袋属性确实可以访问它。
  • 每个函数调用都会创建一个闭包,但没有必要保留闭包,除非可以从闭包外部调用可以访问闭包内部的内部函数。使用返回对象获取垃圾袋函数在这里执行此操作。
  • 闭合是在以下情况下创建的吗制作厨房()被呼叫了?我想说,闭包是由返回获取局部变量引用的语句垃圾袋创建要返回的函数对象时。我认为结局是拥有属性引用的匿名函数获取垃圾袋返回的匿名对象的。(我最近一直在学习Rust,我认为所有权是一个惯例,也有助于用其他语言理顺事情。) 评论 2022年9月30日7:49
  • @MikkoRantalainen,您是正确的,内部函数的闭包不一定是在调用包含函数时创建的,但它必须是在函数返回时创建的或在内部函数传递给其他上下文时创建的(在本例中不发生这种情况)。 评论 2022年10月4日14:59
  • 是的,闭包是在匿名函数已创建定义属性时获取垃圾袋要返回的匿名对象的。 评论 2022年10月6日10:51
628

稻草人

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

相当明显的解决方案

//声明事件处理程序范围之外的计数器var计数器=0;var元素=document.getElementById('按钮');element.addEventListener(“单击”,function(){//递增外部计数器计数器++;if(计数器===3){//每隔三次做一件事log(“第三次很有魅力!”);//重置计数器计数器=0;}});
<button id=“button”>点击我</按钮>

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

考虑这个选项

var元素=document.getElementById('按钮');element.addEventListener(“点击”,(function(){//将计数初始化为0var计数=0;返回函数(e){//<-此函数成为click处理程序count++;//并将保留对上述“count”的访问权限`如果(计数===3){//每隔三次做一件事log(“第三次很有魅力!”);//重置计数器计数=0;}};})());
<button id=“button”>点击我</按钮>

注意这里的一些事情。

在上面的示例中,我使用了JavaScript的闭包行为。此行为允许任何函数无限期地访问创建它的作用域。为了实际应用,我立即调用一个返回另一个函数的函数,因为我返回的函数可以访问内部count变量(因为上面解释的闭包行为),这会导致生成的函数使用一个私有范围。。。不是这么简单吗?让我们稀释一下。。。

简单的单线闭合

//_______________________立即调用______________________//         |                                                                |//|保留使用范围____作为____返回|//|仅通过返回的函数|函数的值||//         |             |            |        |                      |     |//vv v v v vv vvar func=(function(){var a='val';返回函数(){alert(a);};})();

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

func();//警报“val”函数a;//未定义

了解了?因此,在我们的主要示例中,count变量包含在闭包中,并且始终可供事件处理程序使用,因此它在每次单击时都会保持其状态。

此外,此私有变量状态是可访问,用于读取和分配其私有范围的变量。

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

完整博客帖子(包括jQuery注意事项)

0
545

闭包很难解释,因为它们是用来让每个人直觉上都希望能够工作的一些行为发挥作用的。我找到了最好的解释方法(以及学习他们所做的)是想象没有他们的情况:

const makePlus=函数(x){返回函数(y){return x+y;};}const plus5=makePlus(5);控制台.log(加5(3));

如果JavaScript没有知道闭包吗?只需将最后一行中的调用替换为其方法体(这基本上是函数调用所做的),就会得到:

控制台.log(x+3);

现在,什么是x个? 我们没有在当前范围中定义它。唯一的解决方案是加5 携带它的范围(或者更确切地说,它的父范围)。这种方式,x个定义良好,并绑定到值5。

1
  • 闭包只是保存外部词汇环境。如果一个函数是在某个词汇环境中创建的,这意味着它是该词汇环境中记忆的一部分。当我调用该函数时,将创建一个新的执行上下文,并创建一个新词汇环境,它的外部引用将指向创建该函数的词汇环境。 评论 2021年9月23日10:39
422

TLDR公司

闭包是函数与其外部词汇(即as-writen)环境之间的链接,这样,无论何时何地调用函数,在该环境中定义的标识符(变量、参数、函数声明等)都可以从函数内部看到。

细节

在ECMAScript规范的术语中,闭包可以说是由[[环境]]每个函数对象的引用,它指向词汇环境在其中定义函数。

当通过内部调用函数时[[呼叫]]方法[[环境]]将function-object上的引用复制到外部环境参考环境记录新创造的执行上下文(堆栈帧)。

在下面的示例中,函数(f)关闭全局执行上下文的词汇环境:

函数f(){}

在下面的示例中,函数小时关闭函数的词汇环境,这反过来又关闭了全局执行上下文的词法环境。

函数g(){函数h(){}}

如果外部函数返回内部函数,则外部词汇环境将在外部函数返回后持续存在。这是因为如果最终调用内部函数,外部词汇环境需要可用。

在下面的示例中,函数j关闭函数的词汇环境,表示该变量x个从函数内部可见j,长时间后函数已完成执行:

函数i(){var x='mochacchino'返回函数j(){console.log('从函数j:中打印x的值',x)}} 常数k=i()setTimeout(k,500)//500毫秒后调用k(即j)

在闭包中,外部词汇环境中的变量他们自己可用,副本。

函数l(){var y='香草';返回{setY:函数(值){y=值;},logY:函数(值){console.log('y的值是:',y);}}}常数o=l()o.logY()//y的值是:香草o.setY(巧克力)o.logY()//y的值是:巧克力

词汇环境链通过外部环境引用在执行上下文之间链接,形成一个范围链并定义从任何给定函数可见的标识符。

请注意,为了提高清晰度和准确性,这个答案与原来的答案有了很大的不同。

1
408

好吧,6岁的闭门扇。你想听听最简单的闭包示例吗?

让我们想象下一种情况:一个司机坐在车里。那辆车在飞机里。飞机在机场。驾驶员能够在车外和飞机内接近东西,即使飞机离开机场,这也是一种终结。就这样。当你27岁时,看看更详细的解释或在下面的示例中。

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

var平面=功能(默认机场){var lastAirportLeft=默认机场;var汽车={驱动程序:{startAccessPlaneInfo:function(){setInterval(函数(){console.log(“Last airport was”+lastAirportLeft);}, 2000);}}};car.driver.startAccessPlaneInfo();返回{离开机场:功能(airPortName){lastAirportLeft=机场名称;}}}(“Boryspil国际机场”);飞机。离开机场(“约翰·肯尼迪”);

0
385

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

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

前不久,我写了一篇博文解释闭包。下面是我所说的闭包为什么?你会想要一个的。

闭包是让函数具有持久的私有变量-也就是说,只有一个函数知道的变量,它可以在哪里跟踪以前运行时的信息。

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


例子
var iplus1=(函数(){var plusCount=0;返回函数(){return++plusCount;}})();

在这里,外部自我对话匿名函数只运行一次并设置plusCount(加计数)变量设置为0,并返回内部函数表达式。

而内部功能可以访问plusCount(加计数)变量。现在每次我们调用函数时iplus1(),内部函数增加plusCount(加计数)变量。

要记住的重要一点是页面上没有其他脚本可以访问plusCount(加计数)变量和更改plusCount(加计数)变量已通过iplus1型功能。


阅读更多参考:那么,这些是什么?

0
240

最初的问题引用了一句话:

如果你不能向一个六岁的孩子解释,那你自己也真的不明白。

这就是我试图向一个六岁的孩子解释的方式:

你知道成年人是如何拥有一所房子的,他们称之为家吗?当一个母亲有了孩子,孩子实际上什么都不拥有,对吗?但它的父母有一所房子,所以每当有人问“你的家在哪里?”时,孩子都可以回答“那所房子!”,并指向父母的房子。

“封闭”是指孩子能够始终(即使在国外)提及自己的家,即使房子的主人实际上是父母。

0
226

关闭很简单:

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

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

函数make_calculator(){变量n=0;//这个计算器只存储一个数字n返回{添加:函数(a){n+=a;返回n;},乘法:函数(a){n*=a;返回n;}};}first_calculator=生成计算器();second_calculator=生成计算器();first_calculator.add(3);//返回3second_calculator.add(400);//返回400first_calculator.乘法(11);//返回33second_calculator.乘法(10);//返回4000

关键点:每次呼叫制造计算器创建新的局部变量n个,该计算器的添加很久以后的函数制造计算器返回。

如果你熟悉堆栈框架,这些计算器看起来很奇怪:它们怎么能一直访问n个之后制造计算器回报?答案是假设JavaScript不使用“堆栈帧”,而是使用“堆帧”,这可以在使它们返回的函数调用后保持。

内部功能如添加,访问外部函数中声明的变量**,被称为闭包.

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



*例如,它涵盖了另一个答案,除了示例6,它简单地说明了变量可以在声明之前使用,这是一个很好的事实,但与闭包无关。它还涵盖了公认的答案,除了以下几点之外:(1)函数将其参数复制到局部变量中(命名函数参数),以及(2)复制数字会创建一个新数字,但复制对象引用会为同一对象提供另一个引用。这些也很好了解,但又与闭包完全无关。它也与中的示例非常相似这个答案但有点短,不那么抽象。它没有涵盖这个答案这个评论,也就是说JavaScript使得很难插入现在的循环变量在内部函数中的值:“插入”步骤只能使用包含内部函数并在每次循环迭代中调用的helper函数来完成。(严格来说,内部函数访问helper函数的变量副本,而不是插入任何东西。)同样,在创建闭包时非常有用,但不是闭包是什么或它是如何工作的。由于闭包在函数语言(如ML)中的工作方式不同,导致了额外的混乱,其中变量被绑定到值而不是存储空间,提供了一个以某种方式(即“插入”方式)理解闭包的源源不断的人,这对JavaScript来说是不正确的,其中变量始终绑定到存储空间,而从不绑定到值。

**任何外部函数,如果多个函数嵌套,甚至在全局上下文中这个答案明确指出。

0
214

你能给一个5岁的孩子解释一下关门的原因吗*

我仍然认为谷歌的解释效果非常好,简洁明了:

/**当一个函数在另一个函数中定义时*即使在之后也可以访问外部函数的上下文*外部函数返回。**JavaScript中需要学习的重要概念。*/函数outerFunction(someNum){var someString=“嘿!”;var content=文档.getElementById('content');函数innerFunction(){content.innerHTML=someNum+':'+someString;content=null;//DOM引用的Internet Explorer内存泄漏}innerFunction();}外部函数(1);

证明此示例即使内部函数没有返回也会创建闭包

*C#问题

0
190

通过好与坏的比较,我倾向于学习得更好。我喜欢看到工作代码后面跟着可能遇到的非工作代码。我凑在一起一个jsFiddle这是一个比较,并试图将差异归结为我能想到的最简单的解释。

正确关闭:

console.log('CLOSURES DONE RIGHT');var arr=[];函数createClosure(n){返回函数(){返回'n='+n;}}for(var索引=0;索引<10;索引++){arr[index]=创建闭合(索引);}for(arr的var指数){控制台.log(arr[索引]());}
  • 在上述代码中创建关闭(n)在循环的每次迭代中调用。注意,我将变量命名为n个强调它是一个新的在新函数范围中创建的变量,与指数它被绑定到外部范围。

  • 这将创建一个新范围并n个受该范围约束;这意味着我们有10个单独的作用域,每个迭代一个作用域。

  • 创建关闭(n)返回返回该范围内n的函数。

  • 在每个范围内n个绑定到它在何时具有的任何值创建关闭(n)被调用,因此返回的嵌套函数将始终返回n个那是什么时候创建关闭(n)已调用。

关闭错误:

console.log('CLOSURES DONE WRONG');函数createClosureArray(){var badArr=[];for(var索引=0;索引<10;索引++){badArr[index]=函数(){返回'n='+索引;};}返回badArr;}var badArr=创建闭包数组();for(badArr的var索引){console.log(badArr[索引]());}
  • 在上述代码中,循环在创建ClosureArray()函数和函数现在只返回完成的数组,乍一看似乎更直观。

  • 不太明显的是创建ClosureArray()仅在为该函数创建一个作用域时调用,而不是为循环的每个迭代创建一个。

  • 在此函数中,一个名为指数定义。循环运行并将函数添加到返回指数。请注意指数定义在创建ClosureArray只被调用一次的函数。

  • 因为在创建ClosureArray()功能,指数仅绑定到该范围内的值。换句话说,每次循环更改指数,它会更改该范围内引用它的所有内容。

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

  • 循环完成后指数结束值为10,因此添加到数组中的每个函数都返回单个函数的值指数变量,现在设置为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

0
171

闭包维基百科:

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

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

JavaScript中定义范围的构造是一个函数,不像许多其他语言中的代码块,我们通常的意思是关闭JavaScript格式是一个使用已执行的周围函数中定义的非局部变量工作的函数.

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

var db=(函数(){//创建一个隐藏对象,它将保存数据//从外面是无法接近的。var数据={};//制作一个函数,它将提供对数据的一些访问。返回函数(key,val){if(val===未定义){return data[key]}//获取else{return data[key]=val}//设置}//我们正在调用匿名环绕函数,//返回上面的内部函数,它是一个闭包。})();db('x')//->未定义db('x',1)//将x设置为1db('x')//->1//无法访问数据对象本身。//我们能够获得或设置个人信息。

环境管理系统

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

0
142

孩子们永远不会忘记他们与父母分享的秘密,即使他们的父母跑了。这就是函数的闭包。

JavaScript函数的秘密是私有变量

var父级=函数(){var name=“玛丽”;//秘密}

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

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

但是,在JavaScript中,有一件非常特殊的事情,即在其他函数中创建的函数也可以知道父函数的局部变量,并将其保存到生命周期。

var父级=函数(){var name=“玛丽”;var child=函数(childName){//我还可以看到“名字”是“玛丽”}}

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

但可悲的是,如果子函数也是其父函数的私有变量,那么它也会在父函数结束时死亡,而秘密也会随之消失。

所以为了生存,孩子必须在为时已晚之前离开

var父级=函数(){var name=“玛丽”;var child=函数(childName){return“我的名字是”+childName+“,是”+name的孩子;}返回子项;//孩子离开父母->}var child=parent();//<-它在外面

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

所以,如果你叫孩子“爱丽丝”,她会回应的

child(“Alice”)=>“我叫Alice,玛丽的孩子”

这就是要说的。

0
140

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

以下是其中一个示例:

var创建=函数(x){var f=函数(){返回x;//我们可以在这里引用x!};返回f;};//“create”接受一个参数,创建一个函数var g=创建(42);//g是一个现在不带参数的函数var y=g();//这里y是42
0
112

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

这里是一个结束语:

变量a=42;函数b(){return a;}

对。你可能一天用那么多次。


没有理由相信闭包是解决特定问题的复杂设计黑客。不,闭包只是使用来自更高范围的变量从函数的声明位置来看(不是运行).

现在是什么允许你可以做得更精彩,看看其他答案。

1
  • 这确实是一个终结,但答案无法解释任何东西关于它的工作原理,甚至为什么?这是一个结束。特别是,等效代码可以工作,例如在没有闭包的C中。 评论 2021年9月23日10:51
99

闭包是指内部函数可以访问其外部函数中的变量。这可能是闭包最简单的单行解释。

并且内部函数不仅可以访问外部函数的变量,还可以访问以下示例中的外部函数参数

例子

//关闭示例函数addNumbers(firstNumber,secondNumber){var returnValue=“结果为:”;//此内部函数可以访问外部函数的变量和参数函数add(){return returnValue+(firstNumber+secondNumber);}return add();}var结果=addNumbers(10,20);console.log(结果)//30

在此处输入图像描述

0
98

dlaliberte的第一点示例:

闭包不仅是在返回内部函数时创建的。事实上,封闭函数根本不需要返回。相反,您可以将内部函数赋给外部范围中的变量,或将其作为参数传递给另一个可以立即使用它的函数。因此,封闭函数的闭包可能在调用封闭函数时已经存在,因为任何内部函数在调用时都可以访问它。

变量i;函数foo(x){var tmp=3;i=函数(y){控制台.log(x+y+(++tmp));}}foo(2);i(3);
0
89

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

//makeSequencer将返回一个“sequencer”函数var makeSequencer=函数(){var计数=0;//在此功能外无法访问var序列器=函数(){return _ count++;}返回序列器;}var fnext=makeSequencer();var v0=fnext();//v0=0;var v1=fnext();//v1=1;var vz=fnext_count//vz=未定义
0
86

的作者关闭已经很好地解释了闭包,解释了为什么我们需要它们,还解释了理解闭包所必需的LexicalEnvironment。
以下是总结:

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

在此处输入图像描述

在这种情况下,解释器在外面的词汇环境对象。

该过程包括两个步骤:

  1. 首先,创建函数f时,它不是在空的空间。存在当前的LexicalEnvironment对象。在这种情况下上面,它是窗口(a在运行时未定义创建)。

在此处输入图像描述

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

在此处输入图像描述

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

嵌套函数

函数可以相互嵌套,形成LexicalEnvironments链,也可以称为作用域链。

在此处输入图像描述

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

关闭

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

在此处输入图像描述

标记词汇环境:

在此处输入图像描述

正如我们所见,今天说是用户对象中的属性,因此在用户完成后它将继续有效。

如果你还记得,什么时候今天说创建后,它(作为每个函数)将获得一个内部引用这天。[[范围]]到当前的词汇环境。因此,当前用户执行的LexicalEnvironment保留在内存中。User的所有变量也是它的属性,所以它们也会被小心地保存,而不是像通常那样被丢弃。

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

总结如下:

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

这被称为闭包。

85

你要过夜了,你邀请了丹。你告诉丹带一个XBox控制器。

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

功能过夜(HowManyControllers ToBring){var numberOfDansController=howManyControllersToBring;返回函数danInvitedPaul(PaulsControllers的数量){var totalControllers=数字OfDansController+数字OfPaulsController;返回totalControllers;}}var howManyControllersToBring=1;var inviteDan=sleepOver(howManyControllersToBring);//保罗被邀请的唯一原因是丹被邀请了。//所以我们把保罗的邀请设置为丹的邀请。var danInvitedPaul=邀请Dan(howManyControllersToBring);警报(“有”+danInvitedPaul+“控制员被带到派对”);
0
79

JavaScript函数可以访问其:

  1. 论据
  2. 局部变量(即它们的局部变量和局部函数)
  3. 环境,包括:
    • 全局变量,包括DOM
    • 外部函数中的任何内容

如果函数访问其环境,则该函数是一个闭包。

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

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

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

当用户单击VoteUp按钮时,VoteUp_click函数会检查isVotedDown==true以确定是向上投票还是仅取消向下投票。函数voteUp_click是一个闭包,因为它正在访问其环境。

var isVotedUp=false;var isVotedDown=false;函数voteUp_click(){if(isVotedUp)回报;else if(isVotedDown)SetDownVote(false);其他的SetUpVote(true);}函数voteDown_click(){如果(isVotedDown)回报;else if(isVotedUp)SetUpVote(false);其他的SetDownVote(true);}功能SetUpVote(状态){isVotedUp=状态;//对“向上投票”按钮执行一些CSS操作}功能SetDownVote(状态){isVotedDown=状态;//对“投票”按钮执行一些CSS操作}

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

0
60

作为一个6岁孩子的父亲,我目前正在教幼儿(而且是一个没有受过正规教育的编码新手,所以需要纠正),我认为通过动手游戏,这节课会学得最好。如果6岁的孩子已经准备好了解什么是闭幕,那么他们已经长大了,可以自己动手了。我建议将这些代码粘贴到jsfiddle.net中,稍作解释,然后让它们自己编写一首独特的歌曲。下面的解释性文字可能更适合一个10岁的孩子。

功能演唱(人){var firstPart=“有”+个人+“吞咽者”;var fly=函数(){var生物=“一只苍蝇”;var result=“也许她会死”;警报(第一部分+生物+“\n”+结果);};var spider=函数(){var生物=“蜘蛛”;var result=“她体内的那个东西扭动着,抖动着,痒痒着”;警报(第一部分+生物+“\n”+结果);};var bird=函数(){var生物=“一只鸟”;var result=“多么荒谬!”;警报(第一部分+生物+“\n”+结果);};var cat=函数(){var生物=“一只猫”;var result=“想象一下!”;警报(第一部分+生物+“\n”+结果);};fly();蜘蛛();鸟();cat();}var person=“一位老太太”;唱歌(人);

说明书

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

代码:以上所有文字均称为代码。它是用JavaScript编写的。

JAVASCRIPT:JAVASCRIPT是一种语言。就像英语、法语或汉语一样。计算机和其他电子处理器可以理解许多语言。为了让计算机理解JavaScript,它需要一个解释器。想象一下,如果一个只会说俄语的老师来学校教你的课。当老师说“ВсесааДтстта”时,全班都不理解。但幸运的是,你班上有一个俄罗斯学生,他告诉大家这意味着“大家坐下”——你们都这么做了。课堂就像一台电脑,俄罗斯学生是翻译。对于JavaScript,最常见的解释器称为浏览器。

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

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

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

我没有时间去购物,所以功能需要知道我们冰箱里有什么来做决定。每种配料都有不同的烹饪时间,我们希望机器人能在同一时间将所有食物加热。我们需要为该功能提供我们喜欢的数据,该功能可以与冰箱“对话”,该功能可以控制机器人。

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

函数cookMeal(){/*函数内部填充*/}

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

NAME:你可以随意调用函数。“cookMeal”是一个典型的例子,它将两个单词连接在一起,并在第二个单词的开头使用大写字母,但这并不是必需的。它不能有空格,也不能是一个数字。

括号:“括号”或()是JavaScript函数工厂门上的信箱或街道上的信箱,用于向工厂发送信息包。有时邮箱可能会被标记例如 烹饪餐(你、我、你的朋友、我的朋友、冰箱、晚餐时间),在这种情况下,您知道需要提供哪些数据。

胸罩:“胸罩”,看起来像这样{}是我们工厂的有色窗户。从工厂里你可以看到外面,但从外面你看不到里面。

上面的长代码示例

我们的代码以单词开头功能,所以我们知道这是一个!然后是函数的名称-这是我自己对函数的描述。然后是括号()。对于函数,括号总是在那里。有时它们是空的,有时它们里面有东西。这个词的意思是:(人).在这之后有一个这样的支架{。这标志着函数的开始签署()它有一个伙伴,标志着签署()这样地}

函数sing(person){/*函数内部的填充*/}

因此,这个函数可能与唱歌有关,并且可能需要一些关于某人的数据。它内部有指令,可以对数据进行处理。

现在,在函数之后签署(),靠近代码末尾的是行

var person=“一位老太太”;

VARIABLE:字母无功功率,无功功率代表“变量”。变量就像一个信封。这个信封的外面写着“人”。里面有一张纸条,上面写着我们的函数需要的信息,一些字母和空格像一条字符串(称为字符串)连接在一起,组成一个短语,意思是“一位老太太”。我们的信封可以包含其他类型的内容,如数字(称为整数)、指令(称为函数)、列表(称为阵列). 因为这个变量写在所有大括号之外{},因为当您位于大括号内时,可以通过着色窗口看到外面,所以可以从代码中的任何位置看到此变量。我们称之为“全局变量”。

全球变量:是一个全局变量,这意味着如果将其值从“老太太”更改为“年轻人”将一直保持年轻,直到您决定再次更改它,并且代码中的任何其他函数都可以看到它是年轻人。按2012财年按钮或查看选项设置以打开浏览器的开发人员控制台,然后键入“person”查看此值。键入person=“年轻人”更改它,然后再次键入“person”以查看它是否已更改。

在这之后,我们有了线

唱歌(人);

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

“来吧,快来拿!"

当浏览器加载JavaScript代码并到达此行时,它将启动函数。我将行放在末尾,以确保浏览器具有运行它所需的所有信息。

函数定义动作-主要函数是关于唱歌的。它包含一个名为第一部分这适用于对人的歌唱,适用于歌曲的每一节:“有”+“人+”吞下了“。如果您键入第一部分在控制台中,您不会得到答案,因为变量被锁定在函数中&浏览器无法看到大括号的着色窗口。

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

闭包都知道签署()调用函数的变量第一部分是的,因为他们可以从他们的有色窗户看到外面。

闭包之后是线路

fly();蜘蛛();鸟();cat();

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

0
58

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

想象一下,你在整个房子里和你的小兄弟姐妹们玩耍,你拿着玩具四处走动,把其中一些玩具带进了你哥哥的房间。过了一会儿,你哥哥从学校回来,回到他的房间,他锁在里面,所以现在你再也不能直接拿到那里剩下的玩具了。但你可以敲门向你哥哥要玩具。这叫玩具关闭; 你哥哥为你编造的,现在他已经到了外面范围.

与一种情况相比,当一扇门被草稿锁上,里面没有人(一般功能执行),然后发生了一些局部火灾,烧毁了房间(垃圾收集器:D),然后建造了一个新房间,现在你可以在那里留下另一个玩具(新功能实例),但永远不要得到第一个房间实例中留下的玩具。

对于一个高年级的孩子,我会写如下。它不是完美的,但它会让你感觉到它是什么:

兄弟会室内功能游戏(带玩具){//我们把在哥哥房间里玩的玩具收起来。当他回来锁上门时//你哥哥现在应该进入外部[[scope]]对象了。感谢上帝,你可以和他交流。var closureToys=带玩具||[],returnToy,countIt,玩具;//只是另一个闭合助手,供兄弟内部使用。var brotherGivesToyBack=函数(玩具){//新请求。兄弟手里还没有closureToys。给他点时间。returnToy=空;如果(toy&&closureToys.length>0){//如果我们要一个特定的玩具,兄弟会去搜索它。for(countIt=closureToys.length;countIt;countIt--){if(closureToys[countIt-1]==玩具){returnToy='带上你的'+closureToys。splice(countIt-1,1)+',小男孩!';断裂;}}returnToy=returnToy ||“嘿,我在这里找不到任何“+玩具+”。在另一个房间里找;}否则,如果(closureToys.length>0){//否则,只需归还他在房间里的所有东西。returnToy=“看!”+closureToys.join(',')+'.';closureToys=[];}其他{returnToy=“嘿,小虾,我给了你一切!”;}console.log(returnToy);}return兄弟GivesToyBack;}//你正在房子里玩,包括哥哥的房间。var toys=['tedybear','car','jumpingrope'],askBrotherForClosuredToy=在BrothersRoom中玩(玩具);//门被锁上了,哥哥从学校回来了。你不能作弊,直接拿出来。console.log(askBrotherForClosuredToy.closureToys);//未定义//但你可以礼貌地要求你哥哥把它还给我。askBrothersForClosureToy(已关闭);//万岁,在这里,泰迪熊askBrotherForClosuledToy(“球”);//哥哥找不到它。askBrotherForClosuledToy();//剩下的都是哥哥给你的askBrotherForClosuledToy();//里面什么都没有

正如你所见,无论房间是否锁上,留在房间里的玩具仍然可以通过兄弟接触。这里是一个jsbin到处玩。

52

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

闭包是具有状态的函数。它与“this”有点类似,因为“this(这个)”也为函数提供状态,但函数和“this。虽然“this”和函数总是独立存在,但函数不能与其闭包分离,而且语言也无法访问捕获的变量。

因为词汇嵌套函数引用的所有这些外部变量实际上都是其词汇封闭函数链中的局部变量(全局变量可以假设为某些根函数的局部变量),并且函数的每次执行都会创建其局部变量的新实例,因此,每次执行函数时,都会返回一个嵌套函数(或以其他方式将其转出,例如将其注册为回调函数),从而创建一个新的闭包(使用它自己潜在唯一的引用非局部变量集来表示其执行上下文)。

此外,必须理解,JavaScript中的局部变量不是在堆栈框架上创建的,而是在堆上创建的并且只有在没有人引用它们时才被销毁。当函数返回时,对其局部变量的引用将减少,但是,如果在当前执行期间,它们成为闭包的一部分,并且仍然被其词汇嵌套函数引用(只有当这些嵌套函数的引用被返回或以其他方式传输到某些外部代码时,才会发生这种情况),那么它们仍然可以是非空的。

例如:

函数foo(initValue){//当foo函数退出时,此变量不会被销毁。//它由下面返回的两个嵌套函数“捕获”。var值=initValue;//请注意,两个返回的函数是立即创建的。//如果再次调用foo函数,它将返回//引用不同“值”变量的新函数。返回{getValue:function(){return value;},setValue:函数(newValue){value=newValue;}}}功能栏(){//foo将其局部变量“value”设置为5,并返回一个带有//两个函数仍引用该局部变量var obj=foo(5);//提取函数只是为了表明这里不涉及“this”var getValue=obj.getValue;var setValue=对象设置值;警报(getValue())//显示5设置值(10);警报(getValue())//显示10//此时getValue和setValue函数被销毁//(实际上,它们在垃圾收集器的下一次迭代中被销毁)。//foo中的局部变量“value”不再由引用//任何东西都会被摧毁。}bar();
  • 没有人这么说,但你可能是对的。此外,必须理解,JavaScript中的局部变量不是在堆栈框架上创建的,而是在堆上创建的并且只有在没有人引用它们时才被销毁所以我只是告诉大家,我听说原始数据类型存储在堆栈上,而非原始数据类型则存储在堆上,您的意思是说所有数据类型都存储在堆中,而堆栈从未用于任何变量吗?只是问问
    – 优素福
    评论 2023年11月8日18:01
  • 1
    变量的实际存储方式是执行引擎的实现细节。你只是不应该在脑海中想象它们存储在堆栈中,以了解它是如何工作的。这是因为捕获的变量(即使是基元变量!)不能存储在堆栈帧上,因为当函数退出时,其堆栈帧会被破坏,但捕获的局部变量(基元或非基元)必须存在于该时刻之后。当然,为了获得最佳性能,可以在堆栈上创建未捕获的变量。 评论 2023年11月24日10:53
  • 您是有意义的,将封闭变量,甚至是原始变量存储在堆中将达到其目的。但只有关闭的存储在堆中还是所有变量中?我在任何地方都找不到确切的参考,至少是v8?
    – 优素福
    评论 2023年11月24日18:55
51

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

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

函数the_closure(){变量x=4;返回函数(){返回x;//这里,我们回头看看_closure中x的值}}var myFn=关闭();myFn();//=>4

另一种非常简单的解释方法是从范围方面进行解释:

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

0
51

除了最早熟的六岁孩子之外,也许还有一点超出了所有人的想象,但有几个例子帮助我理解了JavaScript中的闭包概念。

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

函数outerFunction(){var outerVar=“猴子”;函数innerFunction(){警报(outerVar);}innerFunction();}outerFunction();

警报:猴子

在上面的示例中,调用了outerFunction,而后者又调用了innerFunction。注意outerVar是如何对innerFunction可用的,这可以通过它正确地警告outerVar的值来证明。

现在考虑以下内容:

函数outerFunction(){var outerVar=“猴子”;函数innerFunction(){返回outerVar;}return innerFunction;}var referenceToInnerFunction=外部函数();警报(referenceToInnerFunction());

警报:猴子

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

这就是事情变得非常有趣的地方。如果我们要删除outerFunction,比如将其设置为null,您可能会认为referenceToInnerFunction将失去对outerVar值的访问。但事实并非如此。

函数outerFunction(){var outerVar=“猴子”;函数innerFunction(){返回outerVar;}return innerFunction;}var referenceToInnerFunction=外部函数();警报(referenceToInnerFunction());outerFunction=空;警报(referenceToInnerFunction());

警报:猴子警报:猴子

但这是怎么回事?既然outerFunction已设置为null,referenceToInnerFunction如何仍然知道outerVar的值?

referenceToInnerFunction仍然可以访问outerVar的值的原因是,当第一次通过将innerFunctional放在outerFunction内部来创建闭包时,innerFuction向其作用域链中添加了对outerFuction作用域(其变量和函数)的引用。这意味着innerFunction有一个指向所有outerFunction变量(包括outerVar)的指针或引用。因此,即使outerFunction完成了执行,或者即使它被删除或设置为null,它的作用域中的变量(如outerVar)也会留在内存中,因为innerFunction中返回给referenceToInnerFunctional的部分对它们的引用很突出。要真正从内存中释放outerVar和outerFunction的其余变量,您必须删除对它们的未完成引用,比如将referenceToInnerFunction设置为null。

//////////

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

函数outerFunction(){var outerVar=“猴子”;函数innerFunction(){警报(outerVar);}outerVar=“大猩猩”;innerFunction();}outerFunction();

警报:大猩猩

其次,创建闭包时,它保留对其所有封闭函数的变量和函数的引用;它不能挑三拣四。但是,闭包应该谨慎使用,因为它们可能占用大量内存;在包含函数完成执行后,许多变量可以长时间保存在内存中。

0
48

我只会把他们指向Mozilla Closures页面这是最好的简明扼要的解释我发现的闭包基础知识和实际用法。强烈建议任何学习JavaScript的人使用。

是的,我甚至向一个6岁的孩子推荐它——如果这个6岁的学生正在学习闭包,那么他们已经准备好理解简明扼要的解释在文章中提供。

0

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.