摘要:當(dāng)在中調(diào)用匿名函數(shù)時(shí),它們用的都是同一個(gè)閉包,而且在這個(gè)閉包中使用了和的當(dāng)前值的值為因?yàn)檠h(huán)已經(jīng)結(jié)束,的值為。最好將閉包當(dāng)作是一個(gè)函數(shù)的入口創(chuàng)建的,而局部變量是被添加進(jìn)這個(gè)閉包的。
閉包不是魔法
這篇文章使用一些簡(jiǎn)單的代碼例子來(lái)解釋JavaScript閉包的概念,即使新手也可以輕松參透閉包的含義。
其實(shí)只要理解了核心概念,閉包并不是那么的難于理解。但是,網(wǎng)上充斥了太多學(xué)術(shù)性的文章,對(duì)于新手來(lái)說(shuō),看完這些文章可能會(huì)更加一頭霧水。
這篇文章面向的是使用主流開(kāi)發(fā)語(yǔ)言的程序員,如果你能讀懂下面這段代碼,恭喜你,你可以開(kāi)始JavaScript閉包的學(xué)習(xí)之旅了。
function sayHello(name) { var text = "Hello" + name; var say = function() { console.log(text); } say(); } sayHello("Joe");
我相信你一定看懂了,那我們就開(kāi)始吧!
舉例之前,我們先用兩句話概括一下:
閉包是支持一類(lèi)函數(shù)特性的一種方式(如果你還不知道什么是一類(lèi)函數(shù),請(qǐng)自行百度);它是一個(gè)表達(dá)式,這個(gè)表達(dá)式可以在其作用域(當(dāng)它被初次定義時(shí))內(nèi)引用變量,或者被賦值給一個(gè)變量,或者被當(dāng)做一個(gè)變量傳遞給某個(gè)函數(shù),甚至被當(dāng)作一個(gè)函數(shù)的執(zhí)行結(jié)果被返回出去。
閉包也可以看作是某個(gè)函數(shù)被調(diào)用時(shí)分配的棧幀,而且當(dāng)這個(gè)函數(shù)返回結(jié)果之后它也不會(huì)被回收(就好像它被分配給了堆,而不是棧)
下面的例子返回了對(duì)一個(gè)方法的引用:
function sayHello2(name){ var text= "Hello" + name; //局部變量 var say=function(){ console.log(text); } return say; } var say2=sayHello2("Bob"); say2();//logs="Hello Bob"
我想大多數(shù)JavaScript程序員都能理解上面代碼中一個(gè)函數(shù)的引用是如何被賦值給一個(gè)變量(say2)的。如果你不清楚的話,最好在繼續(xù)了解閉包之前弄清楚。使用C語(yǔ)言的程序員或許會(huì)認(rèn)為這個(gè)函數(shù)是指向另一個(gè)函數(shù)的指針,并且變量say和say2也同樣是指向函數(shù)的指針。
然而C語(yǔ)言中指向函數(shù)的指針和JavaScript中對(duì)一個(gè)函數(shù)的引用有很大的不同。在JavaScript中,你可以把引用函數(shù)的變量當(dāng)作同時(shí)擁有兩個(gè)指針:一個(gè)指向函數(shù),另一個(gè)隱形地指向閉包。
上面的代碼中生成了一個(gè)閉包是因?yàn)槟涿瘮?shù)function(){console.log(text);}被定義在了另外一個(gè)函數(shù)sayHello2()中。在JavaScript中,如果你在一個(gè)函數(shù)中定義了另外一個(gè)函數(shù),那么你就創(chuàng)建了一個(gè)閉包。
在C語(yǔ)言或者其他流行的開(kāi)發(fā)語(yǔ)言當(dāng)中,函數(shù)返回之后,所有局部變量都不能再被訪問(wèn),因?yàn)闂呀?jīng)被銷(xiāo)毀了。
在JavaScript中,如果在一個(gè)函數(shù)中定義了另外一個(gè)函數(shù),即使從被調(diào)用的函數(shù)中返回,局部變量依然能夠被訪問(wèn)到。正如上面例子中我們?cè)诘玫?b>sayHello2()的返回值之后又調(diào)用了say2()一樣。需要注意到,我們調(diào)用的代碼中引用了函數(shù)sayHello2()中的局部變量text。
function(){console.log(text);} //say2.toString()的輸出結(jié)果;
觀察say2.toString()的輸出結(jié)果,我們會(huì)發(fā)現(xiàn)代碼指向變量text。這個(gè)匿名函數(shù)能夠引用值為Hello Bob的變量text是因?yàn)?b>sayHello2()的局部變量被保留在了閉包中。
在JavaScript中神奇的地方在于引用一個(gè)函數(shù)的同時(shí)會(huì)有一個(gè)秘密的引用指向在這個(gè)函數(shù)內(nèi)部創(chuàng)建的閉包,類(lèi)似于委托一個(gè)方法指針加一個(gè)隱藏的對(duì)象引用。
更多例子當(dāng)你讀到很多關(guān)于閉包的文章時(shí),總會(huì)感覺(jué)一頭霧水,但是當(dāng)你看到一些應(yīng)用的例子時(shí),你就能清晰的理解閉包是如何工作的了。下面是我推薦的一些例子,希望大家能夠認(rèn)真研究直到真正清楚閉包是如何工作的。如果在你沒(méi)有完全理解的情況下就開(kāi)始使用閉包,你很快就會(huì)成為很多奇怪bug的創(chuàng)造者。
下面這個(gè)例子展示了局部變量不是被復(fù)制,而是被保留在了引用當(dāng)中。這是當(dāng)外部函數(shù)存在的情況下將棧幀保存在內(nèi)存中的方法之一。
function say667(){ //處于閉包中的局部變量 var num=42; var say=function(){console.log(num);} num++; return say; } var sayNumber=say667(); sayNumber();//logs 43
下面例子中的三個(gè)全局函數(shù)有對(duì)同一個(gè)閉包的共同引用,因?yàn)樗麄兌荚?b>setupSomeGlobals()中被定義。
var gLogNumber, gIncreaseNumber, gSetNumber; function setupSomeGlobals() { //處于閉包中的局部變量 var num = 42; // 用全局變量存儲(chǔ)對(duì)函數(shù)的引用 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
當(dāng)這三個(gè)函數(shù)被創(chuàng)建時(shí),它們能夠共享對(duì)同一個(gè)閉包的訪問(wèn)-即對(duì)setupSomeGlobals()中的局部變量的訪問(wèn)。
需要注意到在上述例子中,如果你再次調(diào)用setupSomeGlobals(),會(huì)創(chuàng)建一個(gè)新的閉包。gLogNumber()、gSetNumber()和gLogNumber()會(huì)被帶有新閉包的函數(shù)重寫(xiě)(在JavaScript中,當(dāng)在一個(gè)函數(shù)中定義另外一個(gè)函數(shù)時(shí),重新調(diào)用外部函數(shù)會(huì)導(dǎo)致內(nèi)部函數(shù)被重新創(chuàng)建)。
下面這個(gè)例子對(duì)很多人來(lái)說(shuō)都難以理解,所以你更需要真正理解它。在循環(huán)中定義函數(shù)時(shí)要格外小心:閉包中的局部變量或許不會(huì)和你的預(yù)想的一樣。
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]); for (var j = 0; j < fnlist.length; j++) { fnlist[j](); } } testList() //logs "item2 undefined" 3次
注意到result.push( function() {console.log(item + " " + list[i])}向result數(shù)組中插入了三次對(duì)匿名函數(shù)的引用。如果你對(duì)匿名函數(shù)不太熟悉,可以想象成下面的代碼:
pointer=function(){console.log(item+""+list[i])}; result.push(pointer);
需要注意到,當(dāng)你運(yùn)行上面的例子時(shí),item2 undefined被打印了三次!這是因?yàn)橄袂耙粋€(gè)例子中提到的,buildList的局部變量只有一個(gè)閉包。當(dāng)在fnlist[j]()中調(diào)用匿名函數(shù)時(shí),它們用的都是同一個(gè)閉包,而且在這個(gè)閉包中使用了i和item的當(dāng)前值(i的值為3因?yàn)檠h(huán)已經(jīng)結(jié)束,item的值為item2)。因?yàn)槲覀儚?開(kāi)始計(jì)數(shù)所以item的值為item2,而i++會(huì)使i的值變?yōu)?b>3。
下面這個(gè)例子展示了閉包在退出之前包含了外部函數(shù)中定義的任何局部變量。注意到變量alice其實(shí)是在匿名函數(shù)之后定義的。匿名函數(shù)先定義,但是當(dāng)它被調(diào)用時(shí)它能夠訪問(wèn)alice,因?yàn)?b>alice和匿名函數(shù)處于同一作用域(JavaScript會(huì)進(jìn)行變量提升)。sayAlice()()只是直接調(diào)用了sayAlice()返回的函數(shù)引用-但結(jié)果卻和之前一樣,只不過(guò)沒(méi)有臨時(shí)變量而已。
function sayAlice() { var say = function() { console.log(alice); } var alice = "Hello Alice"; return say; } sayAlice()();// logs "Hello Alice"
注意到變量say也在閉包中,能夠被任何在sayAlice()中定義的函數(shù)訪問(wèn),或者在內(nèi)部函數(shù)中被遞歸調(diào)用。
最后一個(gè)例子展現(xiàn)了每次調(diào)用都為局部變量創(chuàng)建一個(gè)獨(dú)立閉包。不是每個(gè)函數(shù)定義都會(huì)有一個(gè)閉包,而是每次函數(shù)調(diào)用產(chǎn)生一個(gè)閉包。
function newClosure(someNum, someRef) { 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;總結(jié)
如果你對(duì)于閉包的概念依然不清晰,那么最好的方式就是運(yùn)行一下上面的例子,看看會(huì)發(fā)生什么。讀懂一篇長(zhǎng)篇大論要比理解一個(gè)例子難的多。我對(duì)與閉包和棧幀的解釋在技術(shù)上并不完全正確-而是為了幫助理解而簡(jiǎn)化了。如果這些基本點(diǎn)都掌握之后,你就可以朝著更細(xì)微之處進(jìn)發(fā)了。
最后總結(jié)幾點(diǎn):
當(dāng)你在一個(gè)函數(shù)中定義另外一個(gè)函數(shù)時(shí),你就使用了閉包。
當(dāng)你在函數(shù)中使用eval()時(shí),你就使用了閉包。你在eval中用到的文字可以指向外部函數(shù)的局部變量,而且在eval中你也可以使用eval("val foo=...")來(lái)創(chuàng)建局部變量。
當(dāng)你在函數(shù)中使用new Function(...)時(shí),不會(huì)創(chuàng)建一個(gè)閉包(這個(gè)新的函數(shù)不能引用外部函數(shù)的局部變量)。
JavaScript中的閉包就好像保存了一份局部變量的備份,他們保持在函數(shù)退出時(shí)的狀態(tài)。
最好將閉包當(dāng)作是一個(gè)函數(shù)的入口創(chuàng)建的,而局部變量是被添加進(jìn)這個(gè)閉包的。
當(dāng)一個(gè)帶有閉包的函數(shù)被調(diào)用時(shí),總會(huì)保存一組新的局部變量。
兩個(gè)看似代碼相同的函數(shù)卻有不同的行為,是因?yàn)?b>隱藏的閉包在作怪。我不認(rèn)為JavaScript代碼能夠判斷出一個(gè)函數(shù)引用是否有閉包。
如果你嘗試做任何動(dòng)態(tài)代碼的改動(dòng)(例如:myFunction = Function(myFunction.toString().replace(/Hello/,"Hola"));),如果myFunction是個(gè)閉包,那就不會(huì)起作用(當(dāng)然,你不會(huì)想在運(yùn)行時(shí)里進(jìn)行源代碼的字符串替換,除非...)。
在函數(shù)中定義多層函數(shù)是有可能的,這樣你就可以得到多個(gè)級(jí)別的閉包。
我認(rèn)為在通常情況下,閉包是函數(shù)及被捕獲的變量的術(shù)語(yǔ),請(qǐng)注意在這篇文章里我沒(méi)有用到閉包的定義。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/87301.html
摘要:如果遇到非常的復(fù)雜的匹配,正則表達(dá)式的優(yōu)勢(shì)就更加明顯了。關(guān)于正則表達(dá)式書(shū)寫(xiě)規(guī)則,可查看,上面說(shuō)的很清楚了,我就不貼出來(lái)了。替換與正則表達(dá)式匹配的子串,并返回替換后的字符串。結(jié)語(yǔ)正則表達(dá)式并不難,懂了其中的套路之后,一切都變得簡(jiǎn)單了。 前言 在正文開(kāi)始前,先說(shuō)說(shuō)正則表達(dá)式是什么,為什么要用正則表達(dá)式?正則表達(dá)式在我個(gè)人看來(lái)就是一個(gè)瀏覽器可以識(shí)別的規(guī)則,有了這個(gè)規(guī)則,瀏覽器就可以幫我們判斷...
摘要:前端異常監(jiān)控如果是移除的流程,那么編程就一定是將放進(jìn)去的流程。過(guò)濾掉運(yùn)行時(shí)錯(cuò)誤上報(bào)加載錯(cuò)誤事件捕獲異常最新的規(guī)范中定義了事件用于全局捕獲對(duì)象沒(méi)有處理器時(shí)異常情況。 前端異常監(jiān)控 如果debug是移除bug的流程,那么編程就一定是將bug放進(jìn)去的流程。如果沒(méi)有用戶反饋問(wèn)題,那就代表我們的產(chǎn)品棒棒噠,對(duì)不對(duì)? 主要內(nèi)容 Web規(guī)范中相關(guān)前端異常 異常按照捕獲方式分類(lèi) 異常的捕獲方式 日志...
摘要:注解在類(lèi)上為類(lèi)提供一個(gè)全參的構(gòu)造方法,加了這個(gè)注解后,類(lèi)中不提供默認(rèn)構(gòu)造方法了。這個(gè)注解用在類(lèi)上,使用類(lèi)中所有帶有注解的或者帶有修飾的成員變量生成對(duì)應(yīng)的構(gòu)造方法。 轉(zhuǎn)載請(qǐng)注明原創(chuàng)地址:http://www.54tianzhisheng.cn/2018/01/07/lombok/ showImg(http://ohfk1r827.bkt.clouddn.com/blog/180107/7...
摘要:一定在發(fā)送請(qǐng)求之前注冊(cè)不管同步或者異步為了讓這個(gè)事件可以更加可靠一定觸發(fā),一定是先注冊(cè)了解同步模式即可,切記不要使用同步模式。至此,我們已經(jīng)大致了解了的基本。一種數(shù)據(jù)描述手段,基本現(xiàn)在的項(xiàng)目不用了,淘汰的原因數(shù)據(jù)冗余太多。 什么是ajax? AJAX 就是瀏覽器提供的一套 API,可以通過(guò) JavaScript 調(diào)用,從而實(shí)現(xiàn)通過(guò)代碼控制請(qǐng)求與響應(yīng)。實(shí)現(xiàn)網(wǎng)絡(luò)編程 1、使用 AJAX 的...
閱讀 2586·2021-08-20 09:38
閱讀 1364·2019-08-30 15:43
閱讀 602·2019-08-29 17:13
閱讀 1614·2019-08-29 14:01
閱讀 1322·2019-08-29 13:29
閱讀 2342·2019-08-23 18:29
閱讀 2056·2019-08-23 17:51
閱讀 1922·2019-08-23 17:16