摘要:深入系列第八篇,介紹理論上的閉包和實(shí)踐上的閉包,以及從作用域鏈的角度解析經(jīng)典的閉包題。定義對(duì)閉包的定義為閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。
定義JavaScript深入系列第八篇,介紹理論上的閉包和實(shí)踐上的閉包,以及從作用域鏈的角度解析經(jīng)典的閉包題。
MDN 對(duì)閉包的定義為:
閉包是指那些能夠訪問(wèn)自由變量的函數(shù)。
那什么是自由變量呢?
自由變量是指在函數(shù)中使用的,但既不是函數(shù)參數(shù)也不是函數(shù)的局部變量的變量。
由此,我們可以看出閉包共有兩部分組成:
閉包 = 函數(shù) + 函數(shù)能夠訪問(wèn)的自由變量
舉個(gè)例子:
var a = 1; function foo() { console.log(a); } foo();
foo 函數(shù)可以訪問(wèn)變量 a,但是 a 既不是 foo 函數(shù)的局部變量,也不是 foo 函數(shù)的參數(shù),所以 a 就是自由變量。
那么,函數(shù) foo + foo 函數(shù)訪問(wèn)的自由變量 a 不就是構(gòu)成了一個(gè)閉包嘛……
還真是這樣的!
所以在《JavaScript權(quán)威指南》中就講到:從技術(shù)的角度講,所有的JavaScript函數(shù)都是閉包。
咦,這怎么跟我們平時(shí)看到的講到的閉包不一樣呢?。?/p>
別著急,這是理論上的閉包,其實(shí)還有一個(gè)實(shí)踐角度上的閉包,讓我們看看湯姆大叔翻譯的關(guān)于閉包的文章中的定義:
ECMAScript中,閉包指的是:
從理論角度:所有的函數(shù)。因?yàn)樗鼈兌荚趧?chuàng)建的時(shí)候就將上層上下文的數(shù)據(jù)保存起來(lái)了。哪怕是簡(jiǎn)單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問(wèn)全局變量就相當(dāng)于是在訪問(wèn)自由變量,這個(gè)時(shí)候使用最外層的作用域。
從實(shí)踐角度:以下函數(shù)才算是閉包:
即使創(chuàng)建它的上下文已經(jīng)銷(xiāo)毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
在代碼中引用了自由變量
接下來(lái)就來(lái)講講實(shí)踐上的閉包。
分析讓我們先寫(xiě)個(gè)例子,例子依然是來(lái)自《JavaScript權(quán)威指南》,稍微做點(diǎn)改動(dòng):
var scope = "global scope"; function checkscope(){ var scope = "local scope"; function f(){ return scope; } return f; } var foo = checkscope(); foo();
首先我們要分析一下這段代碼中執(zhí)行上下文棧和執(zhí)行上下文的變化情況。
另一個(gè)與這段代碼相似的例子,在《JavaScript深入之執(zhí)行上下文》中有著非常詳細(xì)的分析。如果看不懂以下的執(zhí)行過(guò)程,建議先閱讀這篇文章。
這里直接給出簡(jiǎn)要的執(zhí)行過(guò)程:
進(jìn)入全局代碼,創(chuàng)建全局執(zhí)行上下文,全局執(zhí)行上下文壓入執(zhí)行上下文棧
全局執(zhí)行上下文初始化
執(zhí)行 checkscope 函數(shù),創(chuàng)建 checkscope 函數(shù)執(zhí)行上下文,checkscope 執(zhí)行上下文被壓入執(zhí)行上下文棧
checkscope 執(zhí)行上下文初始化,創(chuàng)建變量對(duì)象、作用域鏈、this等
checkscope 函數(shù)執(zhí)行完畢,checkscope 執(zhí)行上下文從執(zhí)行上下文棧中彈出
執(zhí)行 f 函數(shù),創(chuàng)建 f 函數(shù)執(zhí)行上下文,f 執(zhí)行上下文被壓入執(zhí)行上下文棧
f 執(zhí)行上下文初始化,創(chuàng)建變量對(duì)象、作用域鏈、this等
f 函數(shù)執(zhí)行完畢,f 函數(shù)上下文從執(zhí)行上下文棧中彈出
了解到這個(gè)過(guò)程,我們應(yīng)該思考一個(gè)問(wèn)題,那就是:
當(dāng) f 函數(shù)執(zhí)行的時(shí)候,checkscope 函數(shù)上下文已經(jīng)被銷(xiāo)毀了啊(即從執(zhí)行上下文棧中被彈出),怎么還會(huì)讀取到 checkscope 作用域下的 scope 值呢?
以上的代碼,要是轉(zhuǎn)換成 PHP,就會(huì)報(bào)錯(cuò),因?yàn)樵?PHP 中,f 函數(shù)只能讀取到自己作用域和全局作用域里的值,所以讀不到 checkscope 下的 scope 值。(這段我問(wèn)的PHP同事……)
然而 JavaScript 卻是可以的!
當(dāng)我們了解了具體的執(zhí)行過(guò)程后,我們知道 f 執(zhí)行上下文維護(hù)了一個(gè)作用域鏈:
fContext = { Scope: [AO, checkscopeContext.AO, globalContext.VO], }
對(duì)的,就是因?yàn)檫@個(gè)作用域鏈,f 函數(shù)依然可以讀取到 checkscopeContext.AO 的值,說(shuō)明當(dāng) f 函數(shù)引用了 checkscopeContext.AO 中的值的時(shí)候,即使 checkscopeContext 被銷(xiāo)毀了,但是 JavaScript 依然會(huì)讓 checkscopeContext.AO 活在內(nèi)存中,f 函數(shù)依然可以通過(guò) f 函數(shù)的作用域鏈找到它,正是因?yàn)?JavaScript 做到了這一點(diǎn),從而實(shí)現(xiàn)了閉包這個(gè)概念。
所以,讓我們?cè)倏匆槐閷?shí)踐角度上閉包的定義:
即使創(chuàng)建它的上下文已經(jīng)銷(xiāo)毀,它仍然存在(比如,內(nèi)部函數(shù)從父函數(shù)中返回)
在代碼中引用了自由變量
在這里再補(bǔ)充一個(gè)《JavaScript權(quán)威指南》英文原版對(duì)閉包的定義:
This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure in the computer science literature.
閉包在計(jì)算機(jī)科學(xué)中也只是一個(gè)普通的概念,大家不要去想得太復(fù)雜。
必刷題接下來(lái),看這道刷題必刷,面試必考的閉包題:
var data = []; for (var i = 0; i < 3; i++) { data[i] = function () { console.log(i); }; } data[0](); data[1](); data[2]();
答案是都是 3,讓我們分析一下原因:
當(dāng)執(zhí)行到 data[0] 函數(shù)之前,此時(shí)全局上下文的 VO 為:
globalContext = { VO: { data: [...], i: 3 } }
當(dāng)執(zhí)行 data[0] 函數(shù)的時(shí)候,data[0] 函數(shù)的作用域鏈為:
data[0]Context = { Scope: [AO, globalContext.VO] }
data[0]Context 的 AO 并沒(méi)有 i 值,所以會(huì)從 globalContext.VO 中查找,i 為 3,所以打印的結(jié)果就是 3。
data[1] 和 data[2] 是一樣的道理。
所以讓我們改成閉包看看:
var data = []; for (var i = 0; i < 3; i++) { data[i] = (function (i) { return function(){ console.log(i); } })(i); } data[0](); data[1](); data[2]();
當(dāng)執(zhí)行到 data[0] 函數(shù)之前,此時(shí)全局上下文的 VO 為:
globalContext = { VO: { data: [...], i: 3 } }
跟沒(méi)改之前一模一樣。
當(dāng)執(zhí)行 data[0] 函數(shù)的時(shí)候,data[0] 函數(shù)的作用域鏈發(fā)生了改變:
data[0]Context = { Scope: [AO, 匿名函數(shù)Context.AO globalContext.VO] }
匿名函數(shù)執(zhí)行上下文的 AO 為:
匿名函數(shù)Context = { AO: { arguments: { 0: 0, length: 1 }, i: 0 } }
data[0]Context 的 AO 并沒(méi)有 i 值,所以會(huì)沿著作用域鏈從匿名函數(shù) Context.AO 中查找,這時(shí)候就會(huì)找 i 為 0,找到了就不會(huì)往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值為3),所以打印的結(jié)果就是 0。
data[1] 和 data[2] 是一樣的道理。
下一篇文章JavaScript深入之參數(shù)按值傳遞
相關(guān)鏈接如果想了解執(zhí)行上下文的具體變化,不妨循序漸進(jìn),閱讀這六篇:
《JavaScript深入之詞法作用域和動(dòng)態(tài)作用域》
《JavaScript深入之執(zhí)行上下文?!?/p>
《JavaScript深入之變量對(duì)象》
《JavaScript深入之作用域鏈》
《JavaScript深入之從ECMAScript規(guī)范解讀this》
《JavaScript深入之執(zhí)行上下文》
深入系列JavaScript深入系列目錄地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列預(yù)計(jì)寫(xiě)十五篇左右,旨在幫大家捋順JavaScript底層知識(shí),重點(diǎn)講解如原型、作用域、執(zhí)行上下文、變量對(duì)象、this、閉包、按值傳遞、call、apply、bind、new、繼承等難點(diǎn)概念。
如果有錯(cuò)誤或者不嚴(yán)謹(jǐn)?shù)牡胤?,?qǐng)務(wù)必給予指正,十分感謝。如果喜歡或者有所啟發(fā),歡迎star,對(duì)作者也是一種鼓勵(lì)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/82730.html
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
摘要:閉包面試題解由于作用域鏈機(jī)制的影響,閉包只能取得內(nèi)部函數(shù)的最后一個(gè)值,這引起的一個(gè)副作用就是如果內(nèi)部函數(shù)在一個(gè)循環(huán)中,那么變量的值始終為最后一個(gè)值。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第8天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了...
摘要:閉包一認(rèn)識(shí)閉包閉包是一種特殊的對(duì)象。它由兩部分構(gòu)成函數(shù),以及創(chuàng)建該函數(shù)的環(huán)境包含自由變量。環(huán)境由閉包創(chuàng)建時(shí)在作用域中的任何局部變量組成。創(chuàng)建閉包最常見(jiàn)方式,就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)。 閉包 showImg(https://segmentfault.com/img/bVbe3nk?w=1335&h=653); 一、認(rèn)識(shí)閉包 閉包是一種特殊的對(duì)象。它由兩部分構(gòu)成:函數(shù),以及創(chuàng)建該函...
摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問(wèn)外鏈,點(diǎn)擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對(duì)象,在全局環(huán)境中定義的變量就會(huì)綁定到全局對(duì)象中。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第6天。 本...
摘要:理解閉包概念閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。閉包在執(zhí)行后,仍然可以訪問(wèn)內(nèi)部的,因?yàn)閷⒌膬?nèi)的活動(dòng)對(duì)象添加到了的作用域鏈。閉包的應(yīng)用監(jiān)聽(tīng)事件事件錯(cuò)誤的使用循環(huán)使用閉包封裝函數(shù),便于使用私有變量。 理解閉包 概念 閉包是指 有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的 函數(shù)。 函數(shù)式閉包(在內(nèi)部保存數(shù)據(jù)和對(duì)外部無(wú)副作用) 創(chuàng)建方法 在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)(閉包) 原理 普通函數(shù):...
閱讀 3160·2021-11-22 09:34
閱讀 657·2021-11-22 09:34
閱讀 2516·2021-10-08 10:18
閱讀 3446·2021-09-22 15:57
閱讀 2699·2021-09-22 15:25
閱讀 2505·2019-08-30 15:54
閱讀 2256·2019-08-30 15:44
閱讀 1854·2019-08-29 11:18