【翻譯】Javascript 之閉包

原文鏈接

正如標(biāo)題所示,JavaScript閉包對我來說一直有點(diǎn)神秘太示,我讀過許多相關(guān)的文章巷蚪,也在工作中使用過閉包,有時甚至用了閉包而不自知祭务。

最近我去參加了一個講座,最終有人以一種我能理解的方式詮釋了它怪嫌。我會盡量在這篇文章中以這種方式來解釋閉包义锥。我要感謝在CodeSmith上的那些了不起的人,以及他們的 JavaScript難懂系列岩灭。

開篇語

在你能對閉包心領(lǐng)神會之前有些概念是你需要知道的拌倍,其一就是執(zhí)行上下文。
這篇文章是很好的執(zhí)行上下文的入門文章噪径,引用一段:

當(dāng)代碼在JavaScript中運(yùn)行時柱恤,它運(yùn)行的環(huán)境是很重要的,它可能是以下情況之一:
全局代碼 —— 你的代碼第一次被執(zhí)行的默認(rèn)環(huán)境找爱;
函數(shù)代碼 —— 每當(dāng)執(zhí)行流進(jìn)入到函數(shù)體中時梗顺;
(...)
(...),我們可以把術(shù)語執(zhí)行上下文視為評估當(dāng)前代碼的環(huán)境

另一方面缴允,我們是在全局的執(zhí)行上下文中啟動程序的荚守,一些變量是在全局的執(zhí)行上下文中聲明的,我們稱之為全局變量练般。當(dāng)程序調(diào)用函數(shù)時矗漾,發(fā)生了什么呢?以下幾步:

  1. JavaScript創(chuàng)建一個新的局部執(zhí)行上下文薄料;
  2. 該局部執(zhí)行上下文有它自己的變量集合敞贡,這些變量對這個執(zhí)行上下文來說是本地的;
  3. 這個新的執(zhí)行上下文被扔到執(zhí)行棧中摄职。把執(zhí)行棧想象成一個持續(xù)追蹤程序執(zhí)行的機(jī)制誊役。

函數(shù)什么時候運(yùn)行結(jié)束呢?當(dāng)遇到 return 或者關(guān)閉符號 } 時谷市。當(dāng)函數(shù)結(jié)束時蛔垢,會發(fā)生以下幾步:

  1. 局部執(zhí)行上下文從執(zhí)行棧中移除;
  2. 函數(shù)發(fā)送返回值給調(diào)用的上下文迫悠,調(diào)用上下文是調(diào)用這個函數(shù)的執(zhí)行上下文鹏漆,它可能是全局的執(zhí)行上下文或另一個局部執(zhí)行上下文。此時是由調(diào)用執(zhí)行上下文來處理返回值的。返回值可能是對象艺玲,數(shù)組括蝠,函數(shù),boolean值什么的饭聚。如果函數(shù)沒有 return 聲明忌警,則會返回 undefined
  3. 銷毀局部執(zhí)行上下文秒梳。這很重要法绵,銷毀。所有在這個局部執(zhí)行上下文中聲明的變量都會被釋放端幼,它們將失效礼烈。這也是稱它們?yōu)榫植孔兞康脑颉?/li>

一個很基礎(chǔ)的示例

講閉包之前,我們先看下下面這段代碼婆跑,它看起來很直觀,任何在看這篇文章的人應(yīng)該都知道這代碼在干嘛吧庭呜。

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引擎到底是如何工作的滑进,我們來分解一下:

  1. 第一行我們在全局執(zhí)行上下文中聲明了一個新的變量a并賦值為數(shù)字 3;
  2. 接下來就有點(diǎn)棘手了,第二行到第五行是一體的募谎,這里發(fā)生了什么扶关?我們在全局執(zhí)行上下文中聲明了一個名為addTwo的變量,并把它分配給了函數(shù)定義数冬,不管{ }里面的是什么节槐,它們都只屬于addTwo。函數(shù)里面的代碼沒有被評估拐纱,沒有被執(zhí)行铜异,只是存在變量中以供將來使用;
  3. 現(xiàn)在來看第六行秸架。它看起來很簡單揍庄,但這里有太多要分解的地方了。首先我們在全局執(zhí)行上下文中聲明了一個新的變量并標(biāo)識為b东抹。一旦一個變量被聲明它就有了undefined值蚂子;
  4. 接下來,還是在第六行缭黔,我們看到一個賦值運(yùn)算符食茎,準(zhǔn)備給變量b分配一個新值。接下來是一個函數(shù)被調(diào)用馏谨,當(dāng)你看到一個變量后面跟著括號()别渔,就表明這個函數(shù)被調(diào)用了。每個函數(shù)都會有返回值(要不就是一個值,對象钠糊,要不就是undefined)挟秤,不管這個函數(shù)返回什么都將賦值給b;
  5. 但首先我們要先調(diào)用名為addTwo的函數(shù),JavaScript將會在全局執(zhí)行上下文內(nèi)存中查找名為addTwo的變量抄伍,噢~看吶艘刚,找到一個,它是在第二行聲明的截珍。變量addTwo包含了一個函數(shù)定義攀甚,變量a被當(dāng)作一個參數(shù)傳給了該函數(shù)。JavaScript又在全局執(zhí)行上下文內(nèi)存中查找變量a岗喉,找到了秋度,并且它的值是3,然后把數(shù)字3作為參數(shù)傳給函數(shù)钱床,已經(jīng)準(zhǔn)備好執(zhí)行函數(shù)了荚斯;
  6. 現(xiàn)在執(zhí)行上下文將改變。一個新的局部執(zhí)行上下文被創(chuàng)建了查牌,我們暫且把它稱為addTwo 執(zhí)行上下文事期,這個執(zhí)行上下文被推到調(diào)用堆棧中,在局部執(zhí)行上下文中做的第一件事是什么呢纸颜?
  7. 你可能會說 “在局部執(zhí)行上下文中聲明了一個新變量ret” 兽泣。這是不對的,正確答案應(yīng)該是胁孙,先檢查函數(shù)的參數(shù)唠倦。在局部執(zhí)行上下文中聲明新變量x,直到3被當(dāng)作參數(shù)傳過來涮较,變量x被賦值為3稠鼻;
  8. 下一步是:在局部執(zhí)行上下文中聲明新變量ret。它的值現(xiàn)在是undefined;
  9. 還是第三行法希,這有個加法的操作要執(zhí)行枷餐。首先我們需要x的值,JavaScript會查找變量x.它會先在局部執(zhí)行上下文中查找苫亦,然后找到了毛肋,值為 3;第二個操作數(shù)是數(shù)字2屋剑,執(zhí)行加法得出的結(jié)果5被賦值給變量ret;
  10. 第四行润匙,返回了變量ret的內(nèi)容,另一個局部執(zhí)行上下文的查找發(fā)現(xiàn)ret包含數(shù)字5唉匾,然后函數(shù)返回數(shù)字5孕讳,函數(shù)執(zhí)行結(jié)束;
  11. 第四行和第五行匠楚,函數(shù)終止。局部執(zhí)行上下文被銷毀厂财,包括變量xret芋簿,不復(fù)存在,該上下文從調(diào)用堆棧中移出并返回給調(diào)用上下文一個返回值璃饱。在這個案例中調(diào)用上下文是全局執(zhí)行上下文与斤,因為函數(shù)addTwo是在全局執(zhí)行上下文中調(diào)用的;
  12. 現(xiàn)在我們回過頭看一下第四步荚恶,返回值(數(shù)字5)被賦值給變量b撩穿,在這個小程序中我們依舊處于第六行;
  13. 不想說的太細(xì)谒撼,但第七行食寡,變量b的內(nèi)容被打印在控制臺中,輸出值為 5廓潜;

這就是這個非常簡單的程序的冗長解釋抵皱,而我們至今還沒有講到閉包,我保證一定會講的辩蛋,但首先我們還要再繞一兩個彎路叨叙。

詞法作用域

我們需要對詞法作用域有一定的了解,看下面這個例子:

1 let vall = 2;
2 function multiplyThis(n){
3    let ret = n* vall;
4   return ret;
5 }
6 let multiplied = multiplyThis(6);
7 console.log('example of scope:', multiplied);

這里的想法是我們的變量既有在局部執(zhí)行上下文中的也有在全局執(zhí)行上下文中的堪澎,JavaScript的一個復(fù)雜點(diǎn)就是如何查找這些變量。如果它在局部執(zhí)行上下文中找不到這個變量味滞,那么它就會去該變量的調(diào)用執(zhí)行上下文中查找樱蛤,如果還沒找到,就一直重復(fù)這個操作剑鞍,直到在全局執(zhí)行上下文中查找(如果在這也沒找到那這個變量就是undefined).跟著分析這個案例將會更清晰明了昨凡,如果你知道作用域是怎么工作的,那你可以跳過這一步蚁署。

  1. 在全局執(zhí)行上下文中聲明一個新的變量vall 并賦值為2便脊;
  2. 第二行到第五行聲明了一個新變量multiplyThis,并為其分配了一個函數(shù)定義光戈;
  3. 第六行哪痰,在全局執(zhí)行上下文中聲明一個新的變量multiplied
  4. 在全局執(zhí)行上下文內(nèi)存中檢索multiplyThis變量并將它作為函數(shù)執(zhí)行久妆,將數(shù)字6作為參數(shù)傳遞晌杰;
  5. 創(chuàng)建函數(shù)調(diào)用等于創(chuàng)建執(zhí)行上下文——創(chuàng)建一個新的局部執(zhí)行上下文;
    6.在局部執(zhí)行上下文中筷弦,聲明一個變量n并賦值為6肋演;
    7.第三行,在局部執(zhí)行上下文中聲明一個變量ret
  6. 還是第三行爹殊,對變量nvall的內(nèi)容值執(zhí)行乘法操作蜕乡;先在局部執(zhí)行上下文中查找變量n,我們在第六步中聲明了它,它的值是6梗夸;接著在局部執(zhí)行上下文中查找變量vall,先檢查一下調(diào)用上下文层玲,發(fā)現(xiàn)調(diào)用上下文是全局執(zhí)行上下文,那就在全局執(zhí)行上下文中查找vall,看绒瘦,找到了称簿,它是在第一步中聲明的,它的值是2;
  7. 繼續(xù)看第三行惰帽,把兩數(shù)相乘并賦值給變量ret, 6 * 2 = 12.現(xiàn)在ret 的值是12憨降;
  8. 返回ret變量,該局部執(zhí)行上下文被銷毀该酗,包括它的變量retn授药。變量vall沒有被銷毀,因為它是在全局執(zhí)行上下文中的呜魄;
  9. 回到第六行悔叽,在調(diào)用上下文中,數(shù)字12被賦值給變量multiplied;
  10. 最后在第七行爵嗅,在控制臺展示了變量multiplied的值娇澎。

所以在這個示例中,我們要記住函數(shù)有權(quán)限訪問在其調(diào)用上下文中的變量趟庄。專業(yè)名詞就叫做詞法作用域锉试。

一個返回函數(shù)的函數(shù)

在第一個示例中函數(shù)addTwo返回了一個數(shù)字拖云,之前我們說過函數(shù)是可以返回任何值的絮短,現(xiàn)在我們來看一個返回函數(shù)的函數(shù),這對于理解閉包是必不可少的杉允。
這就是我們要來分析的示例:

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);

讓我們繼續(xù)一步步分解。

  1. 第一行繁疤,在全局執(zhí)行上下文中聲明變量val并賦值為7;
  2. 第二到第八行鸣哀,在全局執(zhí)行上下文中聲明了一個名為createAdder的函數(shù)定義架忌。第三行到第七行描述了函數(shù)定義的內(nèi)容叹放,和之前一樣,先不進(jìn)入該函數(shù)速那,先把這個函數(shù)定義存在變量createAdder中捶惜;
  3. 第九行景醇,我們在全局執(zhí)行上下文中聲明了一個名為adder的新變量吝岭,undefined被立即賦值給adder;
  4. 還是第九行,我們看到了括號()纬乍,這時需要執(zhí)行或調(diào)用一個函數(shù)彼城,接下來在全局執(zhí)行上下文的內(nèi)存中查找名為createAdder的變量诅蝶,發(fā)現(xiàn)它在第二步中創(chuàng)建了,調(diào)用它募壕;
  5. 調(diào)用函數(shù)〉骶妫現(xiàn)在我們來看第二行,一個新的局部執(zhí)行上下文被創(chuàng)建了舱馅,我們可以在這個新的執(zhí)行上下文中創(chuàng)建局部變量缰泡。引擎會把這個新的上下文添加到調(diào)用棧中,這個函數(shù)沒有參數(shù)代嗤,可以直接進(jìn)入函數(shù)體中棘钞;
  6. 繼續(xù)看第三到第六行,我們聲明了一個新的函數(shù)干毅,在局部執(zhí)行上下文中創(chuàng)建了變量addNumbers宜猜,這很關(guān)鍵,addNumbers只存在于局部執(zhí)行上下文中硝逢,我們把函數(shù)定義存儲在名為addNumbers的局部變量中姨拥;
  7. 現(xiàn)在來看第七行,我們返回了變量addNumbers的內(nèi)容渠鸽,引擎查找名為addNumbers變量并且找到了叫乌,這是一個函數(shù)定義,沒事徽缚,函數(shù)是可以返回任意值的憨奸,包括函數(shù)定義。所以我們返回了addNumbers的定義凿试,所有第四到第五行{}之間的內(nèi)容構(gòu)成了這個函數(shù)定義排宰;這時也把局部執(zhí)行上下文從調(diào)用棧中移除了似芝;
  8. return之后,局部執(zhí)行上下文被銷毀额各,變量addNumbers也一樣国觉,但函數(shù)聲明還是存在的,它是從函數(shù)中返回的并且被分配給了變量adder,就是我們在第三步中創(chuàng)建的變量虾啦;
  9. 現(xiàn)在看第十行麻诀,在全局執(zhí)行上下文中聲明了變量sum,暫時分配的值是undefined;
  10. 接下來就要執(zhí)行一個函數(shù)了傲醉,哪個函數(shù)呢蝇闭?在名為adder的變量中定義的函數(shù)。我們先在全局執(zhí)行上下文中查找它硬毕,毫無疑問能找到呻引,這是一個需要兩個參數(shù)的函數(shù);
  11. 先來檢索這兩個參數(shù)吐咳,以便調(diào)用這個函數(shù)并正確傳參逻悠;第一個是我們在第一步中定義的變量val,它代表的值是7韭脊;第二個是數(shù)字8童谒;
  12. 現(xiàn)在可以執(zhí)行這個函數(shù)了,函數(shù)的定義在第三到第五行沪羔,然后一個新的局部執(zhí)行上下文又被創(chuàng)建了饥伊,在這個局部上下文中創(chuàng)建了兩個變量ab,它們分別被賦值為78蔫饰,這就是上一步中我們傳遞給函數(shù)的參數(shù)琅豆;
  13. 第四行,一個名為ret的新變量在局部執(zhí)行上下文中被聲明;
  14. 第四行篓吁,執(zhí)行了一個變量ab相加的加法操作茫因,得出的結(jié)果15被賦值給ret變量;
  15. 函數(shù)返回了ret變量的值,局部執(zhí)行上下文被摧毀杖剪,并從調(diào)用棧中移除节腐,變量ab摘盆、ret都不復(fù)存在;
  16. 返回值被賦值給我們在第九步中定義的變量sum;
  17. 在控制臺中打印了sum的值饱苟;

正如所料控制臺會打印15孩擂,我們在這真的繞了一個挺大圈子的,我只是想闡述以下幾點(diǎn):
第一箱熬,函數(shù)定義可以被存儲在變量中类垦,且在它被調(diào)用之前對程序來說都是不可見狈邑;
第二,每當(dāng)一個函數(shù)被調(diào)用蚤认,就會創(chuàng)建一個局部執(zhí)行上下文米苹,當(dāng)函數(shù)結(jié)束時該局部執(zhí)行上下文也會消失;當(dāng)函數(shù)體遇到return 或 關(guān)閉的大括號 }時函數(shù)就結(jié)束了砰琢;

閉包

看下面一段代碼并猜一下會發(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)驗,來快速瀏覽一下這段代碼的執(zhí)行過程陪汽,就如我們期望的那樣運(yùn)行:

  1. 第一到八行训唱,在全局執(zhí)行上下文中創(chuàng)建了一個新變量createCounter,且被分配為函數(shù)定義挚冤;
  2. 第九行况增,在全局執(zhí)行上下文中創(chuàng)建了一個新變量increment
  3. 還是第九行训挡,我們要調(diào)用函數(shù)createCounter澳骤,并把返回值賦值給變量increment
  4. 第一到第八行澜薄,調(diào)用函數(shù)为肮,創(chuàng)建局部執(zhí)行上下文;
  5. 第二行表悬,在局部執(zhí)行上下文中聲明一個名為counter的變量弥锄,數(shù)字0被賦值給 counter
  6. 第三到六行蟆沫,聲明一個名為myFunction的變量籽暇,該變量是在局部執(zhí)行上下文中的,該變量的內(nèi)容是另一個在第四到第五行的函數(shù)定義饭庞;
  7. 返回變量myFunction的內(nèi)容戒悠,局部執(zhí)行上下文被刪除,myFunctioncounter也被銷毀舟山,控制權(quán)回到調(diào)用上下文中绸狐;
  8. 第九行,在調(diào)用上下文中(全局執(zhí)行上下文)累盗, createCounter的返回值被賦值給increment寒矿;現(xiàn)在變量increment包含了一個函數(shù)定義。這個函數(shù)定義就是createCounter返回的若债,它的標(biāo)簽不再是myFunction符相,但具有相同的意義,在全局上下文中,它的標(biāo)簽是increment啊终;
  9. 第十行镜豹,聲明新變量c1;
  10. 還是第十行,查找變量increment蓝牲,它是一個函數(shù)趟脂,調(diào)用它。它包含了前面返回的一個函數(shù)定義(第四到第五行定義的)例衍;
  11. 創(chuàng)建一個新的執(zhí)行上下文昔期,沒有參數(shù),開始執(zhí)行函數(shù)肄渗;
  12. 第四行镇眷,counter = counter + 1 ,在局部執(zhí)行上下文中查找counter的值。我們剛剛創(chuàng)建這個上下文并且沒有聲明任何局部變量翎嫡,在全局執(zhí)行上下文中查找欠动,沒有發(fā)現(xiàn)名為counter的變量,所以JavaScript執(zhí)行的是counter = undefined + 1,聲明一個新的局部變量counter并賦值為1惑申,undefined的值是0;
  13. 第五行具伍,返回counter的內(nèi)容值1,銷毀這個局部執(zhí)行上下文和變量counter;
  14. 回到第十行圈驼,返回值 1被賦值給了c1;
  15. 第十一行人芽,重復(fù)第10-14步,c2也被賦值為1绩脆;
  16. 第十二行萤厅,重復(fù)第10-14步,c3也被賦值為1靴迫;
  17. 第十三行惕味,打印變量c1c2玉锌、c3名挥;

親自試一下看會發(fā)生什么,你會發(fā)現(xiàn)并沒有如我說明的那樣輸出1主守, 1 禀倔,1,而是輸出了1参淫,2救湖,3,那是怎么回事呢涎才?

不知何故鞋既,increment函數(shù)記住了counter的值,這是怎么實現(xiàn)的呢?

難道counter是全局執(zhí)行上下文的一部分嗎涛救?試著打印console.log(counter)得到的是undefined,所以并不是。

或許當(dāng)你調(diào)用increment時业扒,它是從自身被創(chuàng)建的函數(shù)(createCounter)處開始執(zhí)行的检吆?這怎么可能呢,變量increment包含的是函數(shù)定義程储,而不是生成它的函數(shù)蹭沛,所以也不是這個原因。

所以一定是另一個機(jī)制——閉包章鲤,我們終于講到它了摊灭。

這里講一下閉包的工作原理。無論何時你定義一個新的函數(shù)并把它賦值給一個變量時會保存函數(shù)定義败徊,閉包也是如此帚呼。閉包包含了在創(chuàng)建函數(shù)時作用域內(nèi)的所有變量,這有點(diǎn)像一個背包的作用皱蹦。函數(shù)定義自帶一個小背包煤杀,背包里裝著在創(chuàng)建函數(shù)時作用域內(nèi)的所有變量。

所以上面我們的步驟分析全是錯的沪哺,這次我們再準(zhǔn)確地分析一遍沈自。

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. 第一到八行,跟之前一樣辜妓,我們在全局執(zhí)行上下文中創(chuàng)建了一個新的變量createCounter并分配為函數(shù)定義枯途;
  2. 第九行,跟之前一樣籍滴,在全局執(zhí)行上下文中創(chuàng)建了一個新變量increment酪夷;
  3. 還是第九行,跟之前一樣异逐,我們要調(diào)用函數(shù)createCounter捶索,并把返回值賦值給變量increment
  4. 第一到第八行灰瞻,跟之前一樣腥例,調(diào)用函數(shù),創(chuàng)建局部執(zhí)行上下文酝润;
  5. 第二行燎竖,跟之前一樣,在局部執(zhí)行上下文中聲明一個名為counter的變量要销,數(shù)字0被賦值給 counter构回;
  6. 第三到六行,在局部執(zhí)行上下文中聲明一個新的變量myFunction,該變量現(xiàn)在是另一個函數(shù)定義(第四到五行)纤掸,此時我們創(chuàng)建了一個閉包并將其作為函數(shù)定義的一部分脐供。閉包包含了作用域中的變量,這個示例中是counter變量(值為0)借跪;
  7. 第七行政己,返回變量myFunction的內(nèi)容,局部執(zhí)行上下文被刪除掏愁,myFunctioncounter都不存在了歇由。控制權(quán)回到調(diào)用上下文中果港,所以我們返回的是函數(shù)定義和它的閉包沦泌,此時背包中裝著閉包被創(chuàng)建時作用域中的變量;
  8. 第九行辛掠,在調(diào)用上下文中(即全局執(zhí)行執(zhí)行上下文)谢谦,createCounter返回的值被賦值給increment,現(xiàn)在變量increment包含一個函數(shù)定義(和其閉包)公浪,這個函數(shù)定義就是createCounter返回的他宛,它的標(biāo)簽不再是myFunction,但具有相同的意義欠气,在全局上下文中厅各,它是increment
  9. 第十行预柒,聲明一個新變量c1;
  10. 繼續(xù)看第十行队塘,查找變量increment,是一個函數(shù),調(diào)用它宜鸯,它包含一個函數(shù)定義(第四到五行定義的)憔古,還有一個裝著變量的背包;
  11. 創(chuàng)建一個新的執(zhí)行上下文淋袖,不帶參數(shù)鸿市,開始執(zhí)行函數(shù);
  12. 第四行即碗,counter = counter + 1焰情,查找變量counter,在局部或全局執(zhí)行上下文中查找之前,先檢查一下閉包剥懒,在背包中查找内舟。瞧瞧,閉包里包含了一個名為counter的變量初橘,它的值為0验游。在表達(dá)式的最后充岛,它的值被設(shè)置為1耕蝉,并且它的值被再次存入背包中〈薰#現(xiàn)在閉包中包含一個變量counter,其值為1
  13. 第五行垒在,返回counter的內(nèi)容值1炒俱,銷毀局部執(zhí)行上下文爪膊;
  14. 回到第十行,返回值 1被賦值給c1推盛;
  15. 第十一行,重復(fù)第10-14步瘪菌。這次嘹朗,當(dāng)我們查看閉包時师妙,可以看到變量counter的值變?yōu)?code>1了。它是在第12步中第四行代碼被設(shè)置的屹培。它的值增加并且在increment函數(shù)的閉包中存儲的值為2褪秀,c2被賦值為2蓄诽;
  16. 第十二行,重復(fù)第10-14步媒吗,c3被賦值為3仑氛;
  17. 第十三行,打印變量c1闸英、c2锯岖、c3

所以現(xiàn)在我們能理解它的工作原理了自阱,要記住的關(guān)鍵就是當(dāng)聲明函數(shù)的時候嚎莉,它包含了一個函數(shù)定義和一個閉包。閉包就是函數(shù)創(chuàng)建時作用域內(nèi)所有變量的集合沛豌。

你可能會問趋箩,是任何函數(shù)都有閉包嗎赃额,甚至在全局作用域內(nèi)聲明的函數(shù)?是的叫确。全局作用域內(nèi)創(chuàng)建的函數(shù)也會創(chuàng)建閉包跳芳,但是,由于這些函數(shù)是在全局范圍內(nèi)創(chuàng)建的竹勉,因此它們可以訪問全局范圍內(nèi)的所有變量飞盆。與閉包的概念不是真正相關(guān)的。

當(dāng)一個函數(shù)返回函數(shù)時次乓,閉包的概念變得更清晰一點(diǎn)了吓歇。當(dāng)作返回值的那個函數(shù)可以訪問不在全局作用域內(nèi)的函數(shù),但它們僅存在于其閉包中票腰。

沒那么簡單的閉包

有時閉包會在你沒注意時出現(xiàn)城看,你可能看過稱之為局部應(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)

我們聲明了一個通用的帶x參數(shù)并返回另一個函數(shù)的函數(shù)addX测柠。

返回值的參數(shù)也帶一個參數(shù)和變量x相加。

變量x是閉包的一部分缘滥,當(dāng)變量addThree在局部上下文中被聲明的時候轰胁,它被賦值為一個函數(shù)定義和閉包,閉包中包含變量x;

所以當(dāng) addThree被調(diào)用執(zhí)行時朝扼,它是可以在其閉包中訪問到變量x并和當(dāng)作參數(shù)傳過來的變量n相加返回值的赃阀。

在這個例子中控制臺打印的值會是數(shù)字7

結(jié)論

我能一直記得閉包是通過背包類比,當(dāng)一個函數(shù)被創(chuàng)建或被當(dāng)作參數(shù)傳遞或從另一個函數(shù)返回時擎颖,它都帶有一個背包凹耙,并且在背包中裝著聲明函數(shù)時作用域內(nèi)的所有變量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肠仪,一起剝皮案震驚了整個濱河市肖抱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌异旧,老刑警劉巖意述,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吮蛹,居然都是意外死亡荤崇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門潮针,熙熙樓的掌柜王于貴愁眉苦臉地迎上來术荤,“玉大人,你說我怎么就攤上這事每篷“昶荩” “怎么了端圈?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長子库。 經(jīng)常有香客問我舱权,道長,這世上最難降的妖魔是什么仑嗅? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任宴倍,我火速辦了婚禮,結(jié)果婚禮上仓技,老公的妹妹穿的比我還像新娘鸵贬。我一直安慰自己,他們只是感情好脖捻,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布恭理。 她就那樣靜靜地躺著,像睡著了一般郭变。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涯保,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天诉濒,我揣著相機(jī)與錄音,去河邊找鬼夕春。 笑死未荒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的及志。 我是一名探鬼主播片排,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼速侈!你這毒婦竟也來了率寡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤倚搬,失蹤者是張志新(化名)和其女友劉穎冶共,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體每界,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捅僵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了眨层。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片庙楚。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖趴樱,靈堂內(nèi)的尸體忽然破棺而出馒闷,到底是詐尸還是另有隱情酪捡,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布窜司,位于F島的核電站沛善,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏塞祈。R本人自食惡果不足惜金刁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望议薪。 院中可真熱鬧尤蛮,春花似錦、人聲如沸斯议。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哼御。三九已至坯临,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恋昼,已是汗流浹背看靠。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留液肌,地道東北人挟炬。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像嗦哆,于是被迫代替她去往敵國和親谤祖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345