摘要:最近看到的一些文章,終于,有人用于一種讓我明白方式對(duì)閉包進(jìn)行了解釋,我將在本文中嘗試使用這種方法來(lái)解釋閉包。讓我們看一個(gè)返回函數(shù)的函數(shù)示例,因?yàn)檫@對(duì)于理解閉包非常重要。調(diào)用函數(shù)時(shí),執(zhí)行到第行。
正如標(biāo)題所述,JavaScript閉包對(duì)我來(lái)說(shuō)一直有點(diǎn)神秘,看過(guò)很多閉包的文章,在工作使用過(guò)閉包,有時(shí)甚至在項(xiàng)目中使用閉包,但我確實(shí)是這是在使用閉包的知識(shí)。
最近看到的一些文章,終于,有人用于一種讓我明白方式對(duì)閉包進(jìn)行了解釋,我將在本文中嘗試使用這種方法來(lái)解釋閉包。
想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你!
準(zhǔn)備在理解閉包之前,有個(gè)重要的概念需要先了解一下,就是 js 執(zhí)行上下文。
這篇文章是執(zhí)行上下文 很不錯(cuò)的入門教程,文章中提到:
當(dāng)代碼在JavaScript中運(yùn)行時(shí),執(zhí)行代碼的環(huán)境非常重要,并將概括為以下幾點(diǎn):全局作用域——第一次執(zhí)行代碼的默認(rèn)環(huán)境。
函數(shù)作用域——當(dāng)執(zhí)行流進(jìn)入函數(shù)體時(shí)。
(…) —— 我們當(dāng)作 執(zhí)行上下文 是當(dāng)前代碼執(zhí)行的一個(gè)環(huán)境與作用域。
換句話說(shuō),當(dāng)我們啟動(dòng)程序時(shí),我們從全局執(zhí)行上下文中開(kāi)始。一些變量是在全局執(zhí)行上下文中聲明的。我們稱之為全局變量。當(dāng)程序調(diào)用一個(gè)函數(shù)時(shí),會(huì)發(fā)生什么?
以下幾個(gè)步驟:
JavaScript創(chuàng)建一個(gè)新的執(zhí)行上下文,我們叫作本地執(zhí)行上下文。
這個(gè)本地執(zhí)行上下文將有它自己的一組變量,這些變量將是這個(gè)執(zhí)行上下文的本地變量。
新的執(zhí)行上下文被推到到執(zhí)行堆棧中。可以將執(zhí)行堆??醋魇且环N保存程序在其執(zhí)行中的位置的容器。
函數(shù)什么時(shí)候結(jié)束?當(dāng)它遇到一個(gè)return語(yǔ)句或一個(gè)結(jié)束括號(hào)}。
當(dāng)一個(gè)函數(shù)結(jié)束時(shí),會(huì)發(fā)生以下情況:
這個(gè)本地執(zhí)行上下文從執(zhí)行堆棧中彈出。
函數(shù)將返回值返回調(diào)用上下文。調(diào)用上下文是調(diào)用這個(gè)本地的執(zhí)行上下文,它可以是全局執(zhí)行上下文,也可以是另外一個(gè)本地的執(zhí)行上下文。這取決于調(diào)用執(zhí)行上下文來(lái)處理此時(shí)的返回值,返回的值可以是一個(gè)對(duì)象、一個(gè)數(shù)組、一個(gè)函數(shù)、一個(gè)布爾值等等,如果函數(shù)沒(méi)有return語(yǔ)句,則返回undefined。
這個(gè)本地執(zhí)行上下文被銷毀,銷毀是很重要,這個(gè)本地執(zhí)行上下文中聲明的所有變量都將被刪除,不在有變量,這個(gè)就是為什么 稱為本地執(zhí)行上下文中自有的變量。
基礎(chǔ)的例子在討論閉包之前,讓我們看一下下面的代碼:
1: let a = 3 2: function addTwo(x) { 3: let ret = x + 2 4: return ret 5: } 6: let b = addTwo(a) 7: console.log(b)
為了理解JavaScript引擎是如何工作的,讓我們?cè)敿?xì)分析一下:
在第1行,我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)新變量a,并將賦值為3。
接下來(lái)就變得棘手了,第2行到第5行實(shí)際上是在一起的。這里發(fā)生了什么? 我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)名為addTwo的新變量,我們給它分配了什么?一個(gè)函數(shù)定義。兩個(gè)括號(hào){}之間的任何內(nèi)容都被分配給addTwo,函數(shù)內(nèi)部的代碼沒(méi)有被求值,沒(méi)有被執(zhí)行,只是存儲(chǔ)在一個(gè)變量中以備將來(lái)使用。
現(xiàn)在我們?cè)诘?b>6行。它看起來(lái)很簡(jiǎn)單,但是這里有很多東西需要拆開(kāi)分析。首先,我們?cè)谌謭?zhí)行上下文中聲明一個(gè)新變量,并將其標(biāo)記為b,變量一經(jīng)聲明,其值即為undefined。
接下來(lái),仍然在第6行,我們看到一個(gè)賦值操作符。我們準(zhǔn)備給變量b賦一個(gè)新值,接下來(lái)我們看到一個(gè)函數(shù)被調(diào)用。當(dāng)看到一個(gè)變量后面跟著一個(gè)圓括號(hào)(…)時(shí),這就是調(diào)用函數(shù)的信號(hào),接著,每個(gè)函數(shù)都返回一些東西(值、對(duì)象或 undefined),無(wú)論從函數(shù)返回什么,都將賦值給變量b。
但是首先我們需要調(diào)用addTwo的函數(shù)。JavaScript將在其全局執(zhí)行上下文內(nèi)存中查找名為addTwo的變量。噢,它找到了一個(gè),它是在步驟2(或第2 - 5行)中定義的。變量add2包含一個(gè)函數(shù)定義。注意,變量a作為參數(shù)傳遞給函數(shù)。JavaScript在全局執(zhí)行上下文內(nèi)存中搜索變量a,找到它,發(fā)現(xiàn)它的值是3,并將數(shù)字3作為參數(shù)傳遞給函數(shù),準(zhǔn)備好執(zhí)行函數(shù)。
現(xiàn)在執(zhí)行上下文將切換,創(chuàng)建了一個(gè)新的本地執(zhí)行上下文,我們將其命名為“addTwo執(zhí)行上下文”,執(zhí)行上下文被推送到調(diào)用堆棧上。在addTwo執(zhí)行上下文中,我們要做的第一件事是什么?
你可能會(huì)說(shuō),“在addTwo執(zhí)行上下文中聲明了一個(gè)新的變量ret”,這是不對(duì)的。正確的答案是,我們需要先看函數(shù)的參數(shù)。在addTwo執(zhí)行上下文中聲明一個(gè)新的變量x`,因?yàn)橹?b>3是作為參數(shù)傳遞的,所以變量x被賦值為3。
下一步是:在addTwo執(zhí)行上下文中聲明一個(gè)新的變量ret。它的值被設(shè)置為 undefined(第三行)。
仍然是第3行,需要執(zhí)行一個(gè)相加操作。首先我們需要x的值,JavaScript會(huì)尋找一個(gè)變量x,它會(huì)首先在addTwo執(zhí)行上下文中尋找,找到了一個(gè)值為3。第二個(gè)操作數(shù)是數(shù)字2。兩個(gè)相加結(jié)果為5就被分配給變量ret。
第4行,我們返回變量ret的內(nèi)容,在addTwo執(zhí)行上下文中查找,找到值為5,返回,函數(shù)結(jié)束。
第4-5行,函數(shù)結(jié)束。addTwo執(zhí)行上下文被銷毀,變量x和ret被釋放,它們已經(jīng)不存在了。addTwo 執(zhí)行上下文從調(diào)用堆棧中彈出,返回值返回給調(diào)用上下文,在這種情況下,調(diào)用上下文是全局執(zhí)行上下文,因?yàn)楹瘮?shù)addTwo是從全局執(zhí)行上下文調(diào)用的。
現(xiàn)在我們繼續(xù)第4步的內(nèi)容,返回值5被分配給變量b,程序仍然在第6行。
在第7行,b的值 5 被打印到控制臺(tái)了。
對(duì)于一個(gè)非常簡(jiǎn)單的程序,這是一個(gè)非常冗長(zhǎng)的解釋,我們甚至還沒(méi)有涉及閉包。但肯定會(huì)涉及的,不過(guò)首先我們得繞一兩個(gè)彎。
詞法作用域(Lexical scope)我們需要理解詞法作用域的一些知識(shí)。請(qǐng)看下面的例子:
1: let val1 = 2 2: function multiplyThis(n) { 3: let ret = n * val1 4: return ret 5: } 6: let multiplied = multiplyThis(6) 7: console.log("example of scope:", multiplied)
這里想說(shuō)明,我們?cè)诤瘮?shù)執(zhí)行上下文中有變量,在全局執(zhí)行上下文中有變量。JavaScript的一個(gè)復(fù)雜之處在于它如何查找變量,如果在函數(shù)執(zhí)行上下文中找不到變量,它將在調(diào)用上下文中尋找它,如果在它的調(diào)用上下文中沒(méi)有找到,就一直往上一級(jí),直到它在全局執(zhí)行上下文中查找為止。(如果最后找不到,它就是 undefined)。
下面列出向個(gè)步驟來(lái)解釋一下(如果你已經(jīng)熟悉了,請(qǐng)?zhí)^(guò)):
在全局執(zhí)行上下文中聲明一個(gè)新的變量val1,并將其賦值為2。
第2-5行,聲明一個(gè)新的變量 multiplyThis,并給它分配一個(gè)函數(shù)定義。
第6行,聲明一個(gè)在全局執(zhí)行上下文 multiplied 新變量。
從全局執(zhí)行上下文內(nèi)存中查找變量multiplyThis,并將其作為函數(shù)執(zhí)行,傳遞數(shù)字 6 作為參數(shù)。
新函數(shù)調(diào)用(創(chuàng)建新執(zhí)行上下文),創(chuàng)建一個(gè)新的 multiplyThis 函數(shù)執(zhí)行上下文。
在 multiplyThis 執(zhí)行上下文中,聲明一個(gè)變量n并將其賦值為6。
第 3 行。在multiplyThis執(zhí)行上下文中,聲明一個(gè)變量ret。
繼續(xù)第 3 行。對(duì)兩個(gè)操作數(shù) n 和 val1 進(jìn)行乘法運(yùn)算.在multiplyThis執(zhí)行上下文中查找變量 n。我們?cè)诓襟E6中聲明了它,它的內(nèi)容是數(shù)字6。在multiplyThis執(zhí)行上下文中查找變量val1。multiplyThis執(zhí)行上下文沒(méi)有一個(gè)標(biāo)記為 val1 的變量。我們向調(diào)用上下文查找,調(diào)用上下文是全局執(zhí)行上下文,在全局執(zhí)行上下文中尋找 val1。哦,是的、在那兒,它在步驟1中定義,數(shù)值是2。
繼續(xù)第 3 行。將兩個(gè)操作數(shù)相乘并將其賦值給ret變量,6 * 2 = 12,ret 現(xiàn)在值為 12。
返回ret變量,銷毀multiplyThis執(zhí)行上下文及其變量 ret 和 n 。變量 val1 沒(méi)有被銷毀,因?yàn)樗侨謭?zhí)行上下文的一部分。
回到第6行。在調(diào)用上下文中,數(shù)字 12 賦值給 multiplied 的變量。
最后在第7行,我們?cè)诳刂婆_(tái)中打印 multiplied 變量的值
在這個(gè)例子中,我們需要記住一個(gè)函數(shù)可以訪問(wèn)在它的調(diào)用上下文中定義的變量,這個(gè)就是詞法作用域(Lexical scope)。
返回函數(shù)的函數(shù)在第一個(gè)例子中,函數(shù)addTwo返回一個(gè)數(shù)字。請(qǐng)記住,函數(shù)可以返回任何東西。讓我們看一個(gè)返回函數(shù)的函數(shù)示例,因?yàn)檫@對(duì)于理解閉包非常重要??此谧樱?/p>
1: let val = 7 2: function createAdder() { 3: function addNumbers(a, b) { 4: let ret = a + b 5: return ret 6: } 7: return addNumbers 8: } 9: let adder = createAdder() 10: let sum = adder(val, 8) 11: console.log("example of function returning a function: ", sum)
讓我們回到分步分解:
第1行。我們?cè)谌謭?zhí)行上下文中聲明一個(gè)變量val并賦值為 7。
第 2-8 行。我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)名為 createAdder 的變量,并為其分配了一個(gè)函數(shù)定義。第3-7行描述了上述函數(shù)定義,和以前一樣,在這一點(diǎn)上,我們沒(méi)有直接討論這個(gè)函數(shù)。我們只是將函數(shù)定義存儲(chǔ)到那個(gè)變量(createAdder)中。
第9行。我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)名為 adder 的新變量,暫時(shí),值為 undefined。
第9行。我們看到括號(hào)(),我們需要執(zhí)行或調(diào)用一個(gè)函數(shù),查找全局執(zhí)行上下文的內(nèi)存并查找名為createAdder 的變量,它是在步驟2中創(chuàng)建的。好吧,我們調(diào)用它。
調(diào)用函數(shù)時(shí),執(zhí)行到第2行。創(chuàng)建一個(gè)新的createAdder執(zhí)行上下文。我們可以在createAdder的執(zhí)行上下文中創(chuàng)建自有變量。js 引擎將createAdder的上下文添加到調(diào)用堆棧。這個(gè)函數(shù)沒(méi)有參數(shù),讓我們直接跳到它的主體部分.
第 3-6 行。我們有一個(gè)新的函數(shù)聲明,我們?cè)?b>createAdder執(zhí)行上下文中創(chuàng)建一個(gè)變量addNumbers。這很重要,addnumber只存在于createAdder執(zhí)行上下文中。我們將函數(shù)定義存儲(chǔ)在名為 addNumbers` 的自有變量中。
第7行,我們返回變量addNumbers的內(nèi)容。js引擎查找一個(gè)名為addNumbers的變量并找到它,這是一個(gè)函數(shù)定義。好的,函數(shù)可以返回任何東西,包括函數(shù)定義。我們返addNumbers的定義。第4行和第5行括號(hào)之間的內(nèi)容構(gòu)成該函數(shù)定義。
返回時(shí),createAdder執(zhí)行上下文將被銷毀。addNumbers 變量不再存在。但addNumbers函數(shù)定義仍然存在,因?yàn)樗祷夭①x值給了adder 變量。
第10行。我們?cè)谌謭?zhí)行上下文中定義了一個(gè)新的變量 sum,先賦值為 undefined;
接下來(lái)我們需要執(zhí)行一個(gè)函數(shù)。哪個(gè)函數(shù)? 是名為adder變量中定義的函數(shù)。我們?cè)谌謭?zhí)行上下文中查找它,果然找到了它,這個(gè)函數(shù)有兩個(gè)參數(shù)。
讓我們查找這兩個(gè)參數(shù),第一個(gè)是我們?cè)诓襟E1中定義的變量val,它表示數(shù)字7,第二個(gè)是數(shù)字8。
現(xiàn)在我們要執(zhí)行這個(gè)函數(shù),函數(shù)定義概述在第3-5行,因?yàn)檫@個(gè)函數(shù)是匿名,為了方便理解,我們暫且叫它adder吧。這時(shí)創(chuàng)建一個(gè)adder函數(shù)執(zhí)行上下文,在adder執(zhí)行上下文中創(chuàng)建了兩個(gè)新變量 a 和 b。它們分別被賦值為 7 和 8,因?yàn)檫@些是我們?cè)谏弦徊絺鬟f給函數(shù)的參數(shù)。
第 4 行。在adder執(zhí)行上下文中聲明了一個(gè)名為ret的新變量,
第 4 行。將變量a的內(nèi)容和變量b的內(nèi)容相加得15并賦給ret變量。
ret變量從該函數(shù)返回。這個(gè)匿名函數(shù)執(zhí)行上下文被銷毀,從調(diào)用堆棧中刪除,變量a、b和ret不再存在。
返回值被分配給我們?cè)诓襟E9中定義的sum變量。
我們將sum的值打印到控制臺(tái)。
如預(yù)期,控制臺(tái)將打印15。我們?cè)谶@里確實(shí)經(jīng)歷了很多困難,我想在這里說(shuō)明幾點(diǎn)。首先,函數(shù)定義可以存儲(chǔ)在變量中,函數(shù)定義在程序調(diào)用之前是不可見(jiàn)的。其次,每次調(diào)用函數(shù)時(shí),都會(huì)(臨時(shí))創(chuàng)建一個(gè)本地執(zhí)行上下文。當(dāng)函數(shù)完成時(shí),執(zhí)行上下文將消失。函數(shù)在遇到return或右括號(hào)}時(shí)執(zhí)行完成。
最后,一個(gè)閉包看看下面的代碼,并試著弄清楚會(huì)發(fā)生什么。
1: function createCounter() { 2: let counter = 0 3: const myFunction = function() { 4: counter = counter + 1 5: return counter 6: } 7: return myFunction 8: } 9: const increment = createCounter() 10: const c1 = increment() 11: const c2 = increment() 12: const c3 = increment() 13: console.log("example increment", c1, c2, c3)
現(xiàn)在,我們已經(jīng)從前兩個(gè)示例中掌握了它的訣竅,讓我們按照預(yù)期的方式快速執(zhí)行它:
第 1-8 行。我們?cè)谌謭?zhí)行上下文中創(chuàng)建了一個(gè)新的變量createCounter,并賦值了一個(gè)的函數(shù)定義。
第9行。我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)名為increment的新變量。
第9行。我們需要調(diào)用createCounter函數(shù)并將其返回值賦給increment變量。
第 1-8行。調(diào)用函數(shù),創(chuàng)建新的本地執(zhí)行上下文。
第2行。在本地執(zhí)行上下文中,聲明一個(gè)名為counter的新變量并賦值為 0;
第 3-6行。聲明一個(gè)名為myFunction的新變量,變量在本地執(zhí)行上下文中聲明,變量的內(nèi)容是為第4行和第5行所定義。
第7行。返回myFunction變量的內(nèi)容,刪除本地執(zhí)行上下文。變量myFunction 和counter不再存在。此時(shí)控制權(quán)回到了調(diào)用上下文。
第9行。在調(diào)用上下文(全局執(zhí)行上下文)中,createCounter返回的值賦給了increment,變量increment現(xiàn)在包含一個(gè)函數(shù)定義內(nèi)容為createCounter返回的函數(shù)。它不再標(biāo)記為myFunction`,但它的定義是相同的。在全局上下文中,它是的標(biāo)記為labeledincrement。
第10行。聲明一個(gè)新變量 c1。
繼續(xù)第10行。查找increment變量,它是一個(gè)函數(shù)并調(diào)用它。它包含前面返回的函數(shù)定義,如第4-5行所定義的。
創(chuàng)建一個(gè)新的執(zhí)行上下文。沒(méi)有參數(shù),開(kāi)始執(zhí)行函數(shù)。
第4行。counter=counter + 1。在本地執(zhí)行上下文中查找counter變量。我們只是創(chuàng)建了那個(gè)上下文,從來(lái)沒(méi)有聲明任何局部變量。讓我們看看全局執(zhí)行上下文。這里也沒(méi)有counter變量。Javascript會(huì)將其計(jì)算為counter = undefined + 1,聲明一個(gè)標(biāo)記為counter的新局部變量,并將其賦值為number 1,因?yàn)閡ndefined被當(dāng)作值為 0。
第5行。我們變量counter的值 1,我們銷毀本地執(zhí)行上下文和counter變量。
回到第10行。返回值1被賦給c1。
第11行。重復(fù)步驟10-14,c2也被賦值為1。
第12行。重復(fù)步驟10-14,c3也被賦值為1。
第13行。我們打印變量c1 c2和c3的內(nèi)容。
你自己試試,看看會(huì)發(fā)生什么。你會(huì)將注意到,它并不像從我上面的解釋中所期望的那樣記錄1,1,1。而是記錄1,2,3。這個(gè)是為什么?
不知怎么滴,increment函數(shù)記住了那個(gè)cunter的值。這是怎么回事?
counter是全局執(zhí)行上下文的一部分嗎?嘗試 console.log(counter),得到undefined的結(jié)果,顯然不是這樣的。
也許,當(dāng)你調(diào)用increment時(shí),它會(huì)以某種方式返回它創(chuàng)建的函數(shù)(createCounter)?這怎么可能呢?變量increment包含函數(shù)定義,而不是函數(shù)的來(lái)源,顯然也不是這樣的。
所以一定有另一種機(jī)制。閉包,我們終于找到了,丟失的那塊。
它是這樣工作的,無(wú)論何時(shí)聲明新函數(shù)并將其賦值給變量,都要存儲(chǔ)函數(shù)定義和閉包。閉包包含在函數(shù)創(chuàng)建時(shí)作用域中的所有變量,它類似于背包。函數(shù)定義附帶一個(gè)小背包,它的包中存儲(chǔ)了函數(shù)定義創(chuàng)建時(shí)作用域中的所有變量。
所以我們上面的解釋都是錯(cuò)的,讓我們?cè)僭囈淮?,但是這次是正確的。
1: function createCounter() { 2: let counter = 0 3: const myFunction = function() { 4: counter = counter + 1 5: return counter 6: } 7: return myFunction 8: } 9: const increment = createCounter() 10: const c1 = increment() 11: const c2 = increment() 12: const c3 = increment() 13: console.log("example increment", c1, c2, c3)
同上,第1-8行。我們?cè)谌謭?zhí)行上下文中創(chuàng)建了一個(gè)新的變量createCounter,它得到了指定的函數(shù)定義。
同上,第9行。我們?cè)谌謭?zhí)行上下文中聲明了一個(gè)名為increment的新變量。
同上,第9行。我們需要調(diào)用createCounter函數(shù)并將其返回值賦給increment變量。
同上,第1-8行。調(diào)用函數(shù),創(chuàng)建新的本地執(zhí)行上下文。
同上,第2行。在本地執(zhí)行上下文中,聲明一個(gè)名為counter的新變量并賦值為 0 。
第3-6行。聲明一個(gè)名為myFunction的新變量,變量在本地執(zhí)行上下文中聲明,變量的內(nèi)容是另一個(gè)函數(shù)定義。如第4行和第5行所定義,現(xiàn)在我們還創(chuàng)建了一個(gè)閉包,并將其作為函數(shù)定義的一部分。閉包包含作用域中的變量,在本例中是變量counter(值為0)。
第7行。返回myFunction變量的內(nèi)容,刪除本地執(zhí)行上下文。myFunction和counter不再存在。控制權(quán)交給了調(diào)用上下文,我們返回函數(shù)定義和它的閉包,閉包中包含了創(chuàng)建它時(shí)在作用域內(nèi)的變量。
第9行。在調(diào)用上下文(全局執(zhí)行上下文)中,createCounter返回的值被指定為increment,變量increment現(xiàn)在包含一個(gè)函數(shù)定義(和閉包),由createCounter返回的函數(shù)定義,它不再標(biāo)記為myFunction,但它的定義是相同的,在全局上下文中,稱為increment。
第10行。聲明一個(gè)新變量c1。
繼續(xù)第10行。查找變量increment,它是一個(gè)函數(shù),調(diào)用它。它包含前面返回的函數(shù)定義,如第4-5行所定義的。(它還有一個(gè)帶有變量的閉包)。
創(chuàng)建一個(gè)新的執(zhí)行上下文,沒(méi)有參數(shù),開(kāi)始執(zhí)行函數(shù)。
第4行。counter = counter + 1,尋找變量 counter,在查找本地或全局執(zhí)行上下文之前,讓我們檢查一下閉包,瞧,閉包包含一個(gè)名為counter的變量,其值為0。在第4行表達(dá)式之后,它的值被設(shè)置為1。它再次被儲(chǔ)存在閉包里,閉包現(xiàn)在包含值為1的變量 counter。
第5行。我們返回counter的值,銷毀本地執(zhí)行上下文。
回到第10行。返回值1被賦給變量c1。
第11行。我們重復(fù)步驟10-14。這一次,在閉包中此時(shí)變量counter的值是1。它在第12行設(shè)置的,它的值被遞增并以2的形式存儲(chǔ)在遞增函數(shù)的閉包中,c2被賦值為2。
第12行。重復(fù)步驟10-14行,c3被賦值為3。
第13行。我們打印變量c1 c2和c3的值。
你可能會(huì)問(wèn),是否有任何函數(shù)具有閉包,甚至是在全局范圍內(nèi)創(chuàng)建的函數(shù)?答案是肯定的。在全局作用域中創(chuàng)建的函數(shù)創(chuàng)建閉包,但是由于這些函數(shù)是在全局作用域中創(chuàng)建的,所以它們可以訪問(wèn)全局作用域中的所有變量,閉包的概念并不重要。
當(dāng)函數(shù)返回函數(shù)時(shí),閉包的概念就變得更加重要了。返回的函數(shù)可以訪問(wèn)不屬于全局作用域的變量,但它們僅存在于其閉包中。
閉包不是那么簡(jiǎn)單有時(shí)候閉包在你甚至沒(méi)有注意到它的時(shí)候就會(huì)出現(xiàn),你可能已經(jīng)看到了我們稱為部分應(yīng)用程序的示例,如下面的代碼所示:
let c = 4 const addX = x => n => n + x const addThree = addX(3) let d = addThree(c) console.log("example partial application", d)
如果箭頭函數(shù)讓你感到困惑,下面是同樣效果:
let c = 4 function addX(x) { return function(n) { return n + x } } const addThree = addX(3) let d = addThree(c) console.log("example partial application", d)
我們聲明一個(gè)能用加法函數(shù)addX,它接受一個(gè)參數(shù)x并返回另一個(gè)函數(shù)。返回的函數(shù)還接受一個(gè)參數(shù)并將其添加到變量x中。
變量x是閉包的一部分,當(dāng)變量addThree在本地上下文中聲明時(shí),它被分配一個(gè)函數(shù)定義和一個(gè)閉包,閉包包含變量x。
所以當(dāng)addThree被調(diào)用并執(zhí)行時(shí),它可以從閉包中訪問(wèn)變量x以及為參數(shù)傳遞變量n并返回兩者的和 7。
總結(jié)我將永遠(yuǎn)記住閉包的方法是通過(guò)背包的類比。當(dāng)一個(gè)函數(shù)被創(chuàng)建并傳遞或從另一個(gè)函數(shù)返回時(shí),它會(huì)攜帶一個(gè)背包。背包中是函數(shù)聲明時(shí)作用域內(nèi)的所有變量。
代碼部署后可能存在的BUG沒(méi)法實(shí)時(shí)知道,事后為了解決這些BUG,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug。
你的點(diǎn)贊是我持續(xù)分享好東西的動(dòng)力,歡迎點(diǎn)贊!
交流干貨系列文章匯總?cè)缦?,覺(jué)得不錯(cuò)點(diǎn)個(gè)Star,歡迎 加群 互相學(xué)習(xí)。
https://github.com/qq44924588...
我是小智,公眾號(hào)「大遷世界」作者,對(duì)前端技術(shù)保持學(xué)習(xí)愛(ài)好者。我會(huì)經(jīng)常分享自己所學(xué)所看的干貨,在進(jìn)階的路上,共勉!
關(guān)注公眾號(hào),后臺(tái)回復(fù)福利,即可看到福利,你懂的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/53324.html
摘要:最近看到的一些文章,終于,有人用于一種讓我明白方式對(duì)閉包進(jìn)行了解釋,我將在本文中嘗試使用這種方法來(lái)解釋閉包。讓我們看一個(gè)返回函數(shù)的函數(shù)示例,因?yàn)檫@對(duì)于理解閉包非常重要。調(diào)用函數(shù)時(shí),執(zhí)行到第行。 正如標(biāo)題所述,JavaScript閉包對(duì)我來(lái)說(shuō)一直有點(diǎn)神秘,看過(guò)很多閉包的文章,在工作使用過(guò)閉包,有時(shí)甚至在項(xiàng)目中使用閉包,但我確實(shí)是這是在使用閉包的知識(shí)。 最近看到的一些文章,終于,有人用于一...
摘要:函數(shù)式編程的目標(biāo)是盡量寫更多的純函數(shù),并將其與程序的其他部分隔離開(kāi)來(lái)。在函數(shù)式編程中,是非法的。函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸。函數(shù)式編程使用遞歸進(jìn)行循環(huán)。在函數(shù)式編程中,函數(shù)是一級(jí)公民。 showImg(https://segmentfault.com/img/bVblxCO?w=1600&h=710); 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等...
摘要:一旦當(dāng)你理解了一些東西的時(shí)候,卻很容易再一次忘記。但是很快,你會(huì)發(fā)現(xiàn)你已經(jīng)忘記了之前所學(xué)到的一些東西,因此你需要重新復(fù)習(xí)。但是,這次你又忘記了其他的一些東西。你會(huì)感到氣餒,休息一下后,你準(zhǔn)備重新開(kāi)始,卻發(fā)現(xiàn)已經(jīng)忘記了所有的東西。 在學(xué)習(xí)JavaScript中應(yīng)該有過(guò)這樣的經(jīng)歷,比如:? ??? ?? 有些概念容易混淆,特別是當(dāng)你學(xué)習(xí)過(guò)其他語(yǔ)言的時(shí)候。? ?? 很難找到學(xué)習(xí)的時(shí)間(有時(shí)候...
摘要:接下來(lái),我們換一種思路,用一個(gè)相對(duì)較新的來(lái)實(shí)現(xiàn)方法。從這道題目看出,相比考察死記硬背,這樣的實(shí)現(xiàn)更有意義。對(duì)數(shù)組的操作我們不能陌生,其中方法更要做到駕輕就熟。最后,我們?cè)倏聪律鐓^(qū)上著名的和的實(shí)現(xiàn)。 有不少剛?cè)胄械耐瑢W(xué)跟我說(shuō):JavaScript 很多 API 記不清楚怎么辦?數(shù)組的這方法、那方法總是傻傻分不清楚,該如何是好?操作 DOM 的方式今天記,明天忘,真讓人奔潰! 甚至有的開(kāi)發(fā)...
閱讀 896·2021-10-25 09:45
閱讀 3337·2021-09-22 14:58
閱讀 3904·2021-08-31 09:43
閱讀 950·2019-08-30 15:55
閱讀 950·2019-08-29 13:51
閱讀 1259·2019-08-29 13:02
閱讀 3514·2019-08-29 12:52
閱讀 1982·2019-08-26 13:27