摘要:一言以蔽之,閉包,你就得掌握。當(dāng)函數(shù)記住并訪問(wèn)所在的詞法作用域,閉包就產(chǎn)生了。所以閉包才會(huì)得以實(shí)現(xiàn)。從技術(shù)上講,這就是閉包。執(zhí)行后,他的內(nèi)部作用域并不會(huì)消失,函數(shù)依然保持有作用域的閉包。
前言網(wǎng)上總結(jié)閉包的文章已經(jīng)爛大街了,不敢說(shuō)筆者這篇文章多么多么xxx,只是個(gè)人理解總結(jié)。各位看官瞅瞅就好,大神還希望多多指正。此篇文章總結(jié)與《JavaScript忍者秘籍》 《你不知道的JavaScript上卷》
系列博客地址:https://github.com/Nealyang/YOU-SHOULD-KNOW-JS
安利個(gè)人react技術(shù)棧+express+mongoose實(shí)戰(zhàn)個(gè)人博客教程 React-Express-Blog-Demo
為什么我們需要理解并且掌握閉包,且不說(shuō)大道理,就問(wèn)你要不要成為JavaScript高手?不要?那你要不要面試找工作嘛。。。
再者,對(duì)于任何一個(gè)前端er或者JavaScript開(kāi)發(fā)者來(lái)說(shuō),理解閉包可以看做是另一種意義上的重生。閉包是純函數(shù)編程語(yǔ)言的一個(gè)特性,因?yàn)樗蟠蠛?jiǎn)化復(fù)雜的操作,所以很容易在一些JavaScript庫(kù)以及其他高級(jí)代碼中找到閉包的使用。
一言以蔽之,閉包,你就得掌握。
談?wù)勯]包之前,我們先說(shuō)說(shuō)作用域這里我們要說(shuō)的作用域值得是詞法作用域。詞法作用域即為定義在詞法階段的作用域。換句話說(shuō),就是你寫代碼時(shí)將變量和塊作用域?qū)懺谀睦锼鶝Q定的。因此在詞法解析的時(shí)會(huì)保持作用域不變。(JavaScript引擎在運(yùn)行JavaScript代碼的時(shí)候大致經(jīng)過(guò)分詞/詞法分析、解析/語(yǔ)法分析、代碼生成三個(gè)步驟)。
老規(guī)矩,看代碼(就是代碼多~~)
function foo(a) { var b = a*2; function bar(c) { console.log(a,b,c); } bar(b*3); } foo(2);//2 4 12
這個(gè)例子中有三個(gè)逐級(jí)嵌套的作用域,如圖:
截圖來(lái)自《你不知道的JavaScript》
部分一包含整個(gè)作用域也就是全局作用域。其中包含標(biāo)識(shí)符:foo
部分二包含foo所創(chuàng)建的作用域,其中包含:a,bar和b
部分三包含bar所創(chuàng)建的作用域,其中包含:c
這些作用域氣泡的包含關(guān)系給引擎提供了足夠多的位置信息。在上面的代碼中,引擎執(zhí)行console.log的時(shí)候,并查找a,b,c。他首先在最里面的作用域,也就是bar(...)函數(shù)的作用域。引擎無(wú)法在這一層作用域中找到變量a,因此引擎會(huì)去上一級(jí)嵌套作用域foo(...)中查找,如果找到了,則即使用。
如果a,c 都存在作用域bar(...),foo(...)作用域中,console.log(...)即不需要到foo的外部作用域中去查找變量。
無(wú)論函數(shù)在哪里被調(diào)用,且無(wú)論他們?nèi)绾伪徽{(diào)用,他的詞法作用域都只由函數(shù)被聲明的位置決定的。詞法作用域查找只會(huì)查找一級(jí)標(biāo)識(shí)符,比如a,b和c。
簡(jiǎn)單理解詞法作用域的概念,其實(shí)也就是我們常說(shuō)的作用域,關(guān)于JavaScript中欺騙詞法以及更多關(guān)于詞法作用域的介紹,請(qǐng)翻閱《你不知道的JavaScript》。
閉包的概念說(shuō)到閉包的概念,這里還真的比較模糊,我們且看下各種經(jīng)典書(shū)籍給出的概念
《JavaScript權(quán)威指南》中的概念函數(shù)對(duì)象可以通過(guò)作用域鏈互相關(guān)聯(lián)起來(lái),函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)中成為閉包
《JavaScript權(quán)威指南》中的概念閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。
《JavaScript忍者秘籍》中的概念閉包是一個(gè)函數(shù)在創(chuàng)建時(shí)允許該自身函數(shù)訪問(wèn)并操作該自身函數(shù)以外的變量時(shí)所創(chuàng)建的作用域。
《你不知道的JavaScript》中的概念閉包是基于詞法作用域書(shū)寫代碼時(shí)所產(chǎn)生的自然結(jié)果。當(dāng)函數(shù)記住并訪問(wèn)所在的詞法作用域,閉包就產(chǎn)生了。
個(gè)人理解閉包就是一個(gè)函數(shù),一個(gè)可以訪問(wèn)并操作其他函數(shù)內(nèi)部變量的函數(shù)。也可以說(shuō)是一個(gè)定義在函數(shù)內(nèi)部的函數(shù)。因?yàn)镴avaScript沒(méi)有動(dòng)態(tài)作用域,而閉包的本質(zhì)是靜態(tài)作用域(靜態(tài)作用域規(guī)則查找一個(gè)變量聲明時(shí)依賴的是源程序中塊之間的靜態(tài)關(guān)系),所以函數(shù)訪問(wèn)的都是我們定義時(shí)候的作用域,也就是詞法作用域。所以閉包才會(huì)得以實(shí)現(xiàn)。
我們常見(jiàn)的閉包形式就是a 函數(shù)套 b 函數(shù),然后 a 函數(shù)返回 b 函數(shù),這樣 b 函數(shù)在 a 函數(shù)以外的地方執(zhí)行時(shí),依然能訪問(wèn) a 函數(shù)的作用域。其中“b 函數(shù)在 a 函數(shù)以外的地方執(zhí)行時(shí)”這一點(diǎn),才體現(xiàn)了閉包的真正的強(qiáng)大之處。
實(shí)質(zhì)性的問(wèn)題function outer() { var a = 2; function inner() { console.log(a);//2 } inner(); } outer();
基于詞法作用域和查找規(guī)則,inner函數(shù)是可以訪問(wèn)到outer內(nèi)部定義的變量a的。從技術(shù)上講,這就是閉包。但是也可以說(shuō)不是,因?yàn)橛脕?lái)解釋inner對(duì)a的引用方法是詞法作用域的查找規(guī)則,而這些規(guī)則只是閉包中的一部分而已。
下面我們將上面的代碼修改下,讓我們能夠清晰的看到閉包
function outer() { var a = 2; function inner() { console.log(a); } return inner; } var neal = outer(); neal();//2
可能是所有講解閉包的博客中都用爛了的例子了。這里inner函數(shù)被正常調(diào)用執(zhí)行,并且可以訪問(wèn)到outer函數(shù)里定義的變量a。講道理,在outer函數(shù)運(yùn)行后,通常函數(shù)整個(gè)內(nèi)部作用域都會(huì)被銷毀。
而閉包的神奇之處正是如此可以阻止垃圾回收這種事情的發(fā)生,事實(shí)上,內(nèi)部作用域已然存在且拿著a變量,所以沒(méi)有被回收。inner函數(shù)擁有outer函數(shù)內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供inner函數(shù)在之后的任何時(shí)間可以訪問(wèn)。
inner()已然持有對(duì)該作用域的引用,而這個(gè)引用就被叫做閉包。
函數(shù)在定義時(shí)的詞法作用域以外的地方被調(diào)用,閉包使得函數(shù)可以繼續(xù)訪問(wèn)定義時(shí)的詞法作用域。
無(wú)論通過(guò)何種手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會(huì)持有對(duì)原始定義作用域的引用,無(wú)論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包
var fn; function foo() { var a = 2; function baz() { console.log(a); } fn = baz; } function bar() { fn(); } foo(); bar();
上面的代碼不做過(guò)多解釋,挺簡(jiǎn)單,通過(guò)下面的代碼,我們?cè)僬f(shuō)下閉包的三個(gè)有趣的概念
var outerValue = "ninja"; var later; function outFunction() { var innerValue = "Neal"; function innerFunction(param){ console.log(outerValue,innerValue,param,tooLate); } later = innerFunction; } console.log("tooLate is",tooLate); outFunction(); later("Nealyang"); var tooLate = "Haha"; later("Neal_yang"); //tooLate is undefined //ninja Neal Nealyang undefined //ninja Neal Neal_yang Haha
上面代碼運(yùn)行結(jié)果大家可以自行嘗試。總之,從上面的代碼中,我們可以看到閉包的有趣的三個(gè)概念
內(nèi)部函數(shù)的參數(shù)包含在閉包中
作用域之外的所有變量、即便是函數(shù)聲明之后的那些聲明,也都包含在閉包中.
相同作用域內(nèi),尚未聲明的變量,不能進(jìn)行提前引用
代碼處處有閉包function wait(message) { setTimeout( function timer() { console.log( message ); }, 1000 ); } wait( "Hello, closure!" );
如上的代碼,一個(gè)很常見(jiàn)的定時(shí)器,但是timer函數(shù)具有涵蓋wait作用域的閉包,因?yàn)榇诉€保留對(duì)變量Message的引用。
wait執(zhí)行1s后,他的內(nèi)部作用域并不會(huì)消失,timer函數(shù)依然保持有wait作用域的閉包。
深入到引擎內(nèi)部原理中,內(nèi)置的g工具函數(shù)setTimeout持有對(duì)一個(gè)參數(shù)的引用,引擎調(diào)用這個(gè)函數(shù),在例子中就是內(nèi)部的timer函數(shù),而詞法作用域在這個(gè)過(guò)程中保持完整。這就是閉包。
無(wú)論何時(shí)何地,如果將函數(shù)作為第一級(jí)值類型并到處傳遞,你就會(huì)看到閉包在這些函數(shù)中的使用。在定時(shí)器、事件監(jiān)聽(tīng)、Ajax請(qǐng)求、跨窗口通信或者其他異步任務(wù)中,只要使用回調(diào)函數(shù),就在使用閉包。
在經(jīng)典的for循環(huán)中使用閉包for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 ); }
如上for循環(huán),大家都知道輸出6,畢竟這個(gè)作用域中,我們只有一個(gè)i,所有的回調(diào)函數(shù)都是在這個(gè)for循環(huán)結(jié)束以后才執(zhí)行的。
如果我們?cè)噲D假設(shè)循環(huán)中的每一個(gè)迭代在運(yùn)行時(shí)都會(huì)給自己捕獲一個(gè)i的副本,但是根據(jù)作用域的工作原理,盡管循環(huán)中五個(gè)函數(shù)是在各個(gè)迭代中分別定義,但是他們都被封閉在共享的作用域中,因此還是只有一個(gè)i。
所以回到正題,我們需要使用閉包,在每一個(gè)循環(huán)中每一個(gè)迭代都讓他產(chǎn)生一個(gè)閉包作用域。
所以我們代碼修改如下:
for (var i=1; i<=5; i++) { (function() { setTimeout( function timer() { console.log( i ); }, i*1000 ); })(); }
but!!!你也發(fā)現(xiàn)了,這樣并不姓,不是IIFE會(huì)產(chǎn)生一個(gè)閉包的么?是的沒(méi)錯(cuò),但是如果這個(gè)IIFE產(chǎn)生的閉包作用域是可空的,那么將它封裝起來(lái)又有什么意義呢?所以它需要點(diǎn)實(shí)質(zhì)性的東西,讓我們?nèi)ナ褂谩?/p>
for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })( i ); }
當(dāng)然,如上問(wèn)題我們可以使用es6中的let來(lái)解決。但是這里就不做過(guò)多說(shuō)明了。大家可以自行Google。
模塊這個(gè)部分比較簡(jiǎn)單好理解,因?yàn)殚]包可以很好形成塊級(jí)作用域,對(duì)內(nèi)部變量有很好的隱藏。所以自然我們可以將其作為模塊開(kāi)發(fā)的手段。撇開(kāi)如今的export、import不談
直接看例子就好,操作比較常規(guī)
function foo() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( " ! " ) ); } return { doSomething:doSomething, doAnother:doAnother } }
簡(jiǎn)單說(shuō)明下,doSomething和doAnother函數(shù)具有涵蓋模塊實(shí)例內(nèi)部作用域的閉包。當(dāng)通過(guò)返回一個(gè)含有屬性引用的對(duì)象的方式來(lái)將函數(shù)傳遞到詞法作用域外部,我們已經(jīng)創(chuàng)造了可以觀察和實(shí)踐的 閉包條件。
必須有外部的封閉函數(shù),該函數(shù)必須至少被調(diào)用一次
封閉函數(shù)必須返回至少一個(gè)內(nèi)部函數(shù),這樣內(nèi)部函數(shù)才能在私有作用域中形成閉包,并且可以訪問(wèn)或修改私有的狀態(tài)。
當(dāng)然,上面的代碼我們還可以寫成IIFE的形式。但是畢竟市場(chǎng)上講解閉包的好文是在太多,這里我們就點(diǎn)到為止。
交流掃碼關(guān)注我的個(gè)人微信公眾號(hào),分享更多原創(chuàng)文章。點(diǎn)擊交流學(xué)習(xí)加我微信、qq群。一起學(xué)習(xí),一起進(jìn)步
歡迎兄弟們加入:
Node.js技術(shù)交流群:209530601
React技術(shù)棧:398240621
前端技術(shù)雜談:604953717 (新建)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/89554.html
摘要:到底什么是閉包這個(gè)問(wèn)題在面試是時(shí)候經(jīng)常都會(huì)被問(wèn),很多小白一聽(tīng)就懵逼了,不知道如何回答好。上面這么說(shuō)閉包是一種特殊的對(duì)象。閉包的注意事項(xiàng)通常,函數(shù)的作用域及其所有變量都會(huì)在函數(shù)執(zhí)行結(jié)束后被銷毀。從而使用閉包模塊化代碼,減少全局變量的污染。 閉包,有人說(shuō)它是一種設(shè)計(jì)理念,有人說(shuō)所有的函數(shù)都是閉包。到底什么是閉包?這個(gè)問(wèn)題在面試是時(shí)候經(jīng)常都會(huì)被問(wèn),很多小白一聽(tīng)就懵逼了,不知道如何回答好。這個(gè)...
摘要:談起閉包,它可是兩個(gè)核心技術(shù)之一異步基于打造前端持續(xù)集成開(kāi)發(fā)環(huán)境本文將以一個(gè)標(biāo)準(zhǔn)的項(xiàng)目為例,完全拋棄傳統(tǒng)的前端項(xiàng)目開(kāi)發(fā)部署方式,基于容器技術(shù)打造一個(gè)精簡(jiǎn)的前端持續(xù)集成的開(kāi)發(fā)環(huán)境。 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果讀完本文還不懂,可以揍我。 不論你是javascript新手還是老鳥(niǎo),不論是面試求職,還是日...
摘要:另外回答的時(shí)候要淡定,一些問(wèn)題就算不懂也不能慌,要和面試官談笑風(fēng)生,然后盡量扯回到自己懂的東西上面大公司比如百度給我的感覺(jué)就是很重視基礎(chǔ)思維和潛力。 —— 雖然我的offer少,但是我的拒信多啊 這幾天終于閑下來(lái),做一點(diǎn)微小的工作,整理了一些之前幾家公司的前端面試題和個(gè)人經(jīng)驗(yàn),想做前端的師弟妹可以參考,也歡迎各同行大神來(lái)指教~ (以下問(wèn)題不分先后,時(shí)間久遠(yuǎn)難免有些遺漏;很多問(wèn)題面試官都...
摘要:另外回答的時(shí)候要淡定,一些問(wèn)題就算不懂也不能慌,要和面試官談笑風(fēng)生,然后盡量扯回到自己懂的東西上面大公司比如百度給我的感覺(jué)就是很重視基礎(chǔ)思維和潛力。 —— 雖然我的offer少,但是我的拒信多啊 這幾天終于閑下來(lái),做一點(diǎn)微小的工作,整理了一些之前幾家公司的前端面試題和個(gè)人經(jīng)驗(yàn),想做前端的師弟妹可以參考,也歡迎各同行大神來(lái)指教~ (以下問(wèn)題不分先后,時(shí)間久遠(yuǎn)難免有些遺漏;很多問(wèn)題面試官都...
閱讀 1625·2021-11-02 14:42
閱讀 521·2021-10-18 13:24
閱讀 939·2021-10-12 10:12
閱讀 1817·2021-09-02 15:41
閱讀 3201·2019-08-30 15:56
閱讀 2874·2019-08-29 16:09
閱讀 2056·2019-08-29 11:13
閱讀 3617·2019-08-28 18:06