You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functionsay667(){// Local variable that ends up within closurevarnum=42;varsay=function(){console.log(num);}num++;returnsay;}varsayNumber=say667();sayNumber();// logs 43
vargLogNumber,gIncreaseNumber,gSetNumber;functionsetupSomeGlobals(){// Local variable that ends up within closurevarnum=42;// Store some references to functions as global variablesgLogNumber=function(){console.log(num);}gIncreaseNumber=function(){num++;}gSetNumber=function(x){num=x;}}setupSomeGlobals();gIncreaseNumber();gLogNumber();// 43gSetNumber(5);gLogNumber();// 5varoldLog=gLogNumber;setupSomeGlobals();gLogNumber();// 42oldLog()// 5
functionsayAlice(){varsay=function(){console.log(alice);}// Local variable that ends up within closurevaralice='Hello Alice';returnsay;}sayAlice()();// logs "Hello Alice"
functionbuildList(list){varresult=[];for(vari=0;i<list.length;i++){varitem='item'+i;result.push(function(){console.log(item+' '+list[i])});}returnresult;}functiontestList(){varfnlist=buildList([1,2,3]);// Using j only to help prevent confusion -- could use i.for(varj=0;j<fnlist.length;j++){fnlist[j]();}}testList()//logs "item2 undefined" 3 times
前言
原先在老博客里面有写过闭包相关知识,本以为懂了,但是前两天思考惰性求值时又有点迷糊了,这东西看来我还没到融会贯通的地步,只靠理论是不行的,还是要写例子,并且多写,闭包是典型的孰能生巧的技术。
原先博客里的闭包保留,这里主要翻译介绍stackoverflow上高达6055票的高赞回答:How do JavaScript closures work?,回答的还真不错,翻译过来既让自己再度学习,也给看到的人输送“国际知识”。
翻译正文
闭包并不是魔法
这篇文章讲解闭包是为了让程序员能够理解并通过js使用它。并不是为专家或功能程序员(不知道翻译对不对)而写的。
一旦你领悟到了闭包的核心概念,那么对于你来说闭包并不难以理解。但是,我们不能仅通过阅读学术文章或者一些学术方面的知识去理解闭包。
这篇文章是针对具有主流语言编程经验的程序员,能够阅读并理解接下来的js函数:
一个关于闭包的例子
两句话总结:
下面的代码返回了对函数的引用:
大多数JavaScript程序员能理解如何将一个函数的引用返回给上述代码中的变量(
say2
)。如果你不理解,在学习闭包之前你应该先看看这方面的知识。使用C语言的程序员可能会认为这里的函数是作为指向另一个函数的指针然后被返回,并且会认为变量say
和say2
都是指向函数的指针。C语言里面指向函数的指针和js里面函数的引用有非常关键的区别。在js里面,你可以将一个函数引用变量想象成既有指向函数的指针又指向闭包的隐藏指针。
上述代码中存在闭包,因为匿名函数
function() { console.log(text);}
声明在另一个函数(上例中的sayHello2()
)的内部。在js世界里,如果你在一个函数内部使用了function
关键字,那么你正在创建一个闭包!在C或者其他大多数与C相似的语言中,函数返回后,所有函数内的局部变量不能外部被获取,因为堆栈帧被销毁了。
在js中,如果你在一个函数内部声明另一个函数,当你调用返回的函数时,局部变量保持可访问状态。上面已经说明了,因为我们在函数
sayHello2()
返回之后才调用函数say2()
。注意在上述代码中,我们调用了函数sayHello2()
中局部变量text
的引用。观察
say2.toString()
的输出,我们可以看到代码引用了变量text
。匿名函数为什么可以引用保存了值为**'Hello Bob'变量text
呢?这是因为函数sayHello2()
的局部变量被保存在了闭包**内!神奇的是,在JavaScript中,一个函数引用也对它创建的闭包有一个秘密引用——类似于委托是方法指针加上对对象的秘密引用。
更多的例子
因为某些原因,闭包似乎真的很难理解当你去阅读相关理论的话。但是当你看到一些例子的话,闭包是如何起作用的将会变得清晰易懂(我花了一些时间去理解它)。我建议仔细研究这些例子,直到你明白它们是如何工作的。如果你在没有完全理解它们如何工作的情况下开始使用闭包,你很快就会创建一些非常奇怪的bugs!
例3
这个例子表明局部变量不是被复制的——而是通过引用来保存的。这就好像当外部函数退出时在内存中保留一个栈帧!
例4
所有这三个全局函数都有一个对同一个闭包的共同引用,因为它们都是在一次调用
setupSomeGlobals()
中被赋值。这三个函数共享了同一个闭包——它们都被赋值引用函数
setupSomeGlobals()
的局部函数。(译者注:一开始定义了三个全局变量,但是没有赋值,当调用setupSomeGlobals()
后,三个全局变量在该函数内被赋值,通过引用保存了三个不同的局部函数,他们三个与外部setupSomeGlobals()
一起构成了一个闭包,所以他们三共享同一个闭包内的所有变量)。注意上面的例子,如果你又一次调用
setupSomeGlobals()
,那么会创建一个新的闭包。原先的gLogNumber
,gIncreaseNumber
,gSetNumber
三个变量所保存的引用将会被新闭包内的新函数覆盖。(在js中,无论何时你在一个函数中声明另一个函数,每次外部函数被调用时,其内部的函数也会被重新创建。)例5
这个例子表明闭包包含所有在外部函数退出前——声明在其内部的局部变量。注意观察,局部变量
alice
事实上是在匿名函数之后声明的。匿名函数先被声明,但是当这个匿名函数被调用时它却可以获得局部变量alice
,这是因为alice
与匿名函数在相同的作用域内(JavaScript做了变量提升)。另外sayAlice()()
只是直接调用从sayAlice()
返回的函数引用——它与之前的做法完全相同,但没有临时变量。提示:注意变量
say
也在闭包内,可以被任何在sayAlice()
内部声明的其它函数访问,或者它也可以在内部函数中递归访问。例6(我栽在了这里)
这对许多人来说是一个真正的难题,所以你需要了解它。如果要在循环中定义函数,请务必小心:闭包中的局部变量可能并不像你想象中的那样工作。
您需要了解Javascript中的“变量提升”功能才能理解此示例。
result.push( function() {console.log(item + ' ' + list[i])}
这段代码向resule
这个数组中添加了3次对同一个匿名函数的引用。如果你对匿名函数不熟悉,不妨想象成下面这样:注意当你运行这个例子时,
item2 undefined
被打印三次!就跟先前的例子一样,对函数buildList
内的局部变量(result
,i
,item
)而言只有一个闭包。在fnlist[j]()
这一行时调用匿名函数;他们都使用相同的单个闭包,并且他们使用该闭包内的当前i
和item
值。(这里i
值为3,因为循环已经完成并且item
值为item2)。请注意,我们从0开始索引,因此item
的值为item2。 而i ++
会将i
增加到3。(译者注:我就是在这里踩得坑,我以为i === 2
,但是接下来i
需要自增1来结束循环,最终闭包内保存的应该是结束循环时i
的值;至于item
为何不是item3,因为当i === 3
时不满足循环条件,循环结束,所以item
是item2)。当使用块级作用域声明变量
item
(通过let
)代替用var
声明的函数作用域变量看看会发生什么,可能会有帮助。如果发生了这种变化,那么数组result
中的每个匿名函数都有自己的闭包(译者注:将var item = 'item' + i
改为let item = 'item' + i
);那么这个例子输出将会像下面这样:如果变量
i
也用let
声明,输出会变成下面这样:例7
最后一个例子,每次调用主函数(外部函数)都会创建一个独立的闭包。
概要
如果一切似乎完全不清楚,那么最好的办法就是通过例子去实践。阅读解释(纯理论知识)比理解示例要困难得多。我对闭包和栈帧的解释等在技术上并不正确——它们都是为了帮助理解而进行的粗略简化。一旦基本想法得到了解决,您可以稍后获取详细信息。
最后一点:
eval()
,都会使用闭包。你eval()
的内容可以引用局部变量,你甚至可以在eval()
内创建新的局部变量通过eval('var foo = …')
这种方式。new Function(...)
(函数构造器),却不会创建一个闭包。(这个新的函数不能引用外部函数的局部变量)myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));
),如果myFunction
是一个闭包的话这段代码不会起作用(当然,你永远不会想到在运行时做源代码字符串替换,但是。。。)链接
感谢
如果你刚刚学习闭包(无论在哪!),我对任何有关您可能建议的更改可能会使本文变得更清楚的任何反馈感兴趣。发送邮件到morrisjohns.com(morris_closure @)。 请注意,我不是JavaScript的专家,也不是闭包。
The text was updated successfully, but these errors were encountered: