總結(jié):閉包的核心是[[scope]]屬性誓斥,在函數(shù)解析過(guò)程中只洒,如果函數(shù)引用了外層函數(shù)的變量,那么外層函數(shù)(即使自身被銷(xiāo)毀)的活動(dòng)對(duì)象帶著對(duì)應(yīng)變量將會(huì)被保留劳坑,并且記錄在scope屬性中毕谴,作為作用域鏈的第二層,如果還引用了外層函數(shù)的外層函數(shù)的變量距芬,那么對(duì)應(yīng)的活動(dòng)對(duì)象與變量也會(huì)被保留涝开,并記錄,將會(huì)作為作用域鏈的第三層蔑穴,依次類(lèi)推...忠寻。當(dāng)函數(shù)被調(diào)用時(shí),所取到的外部變量的值將會(huì)是調(diào)用時(shí)各個(gè)變量的值存和。即當(dāng)前值奕剃。
閉包調(diào)用時(shí)也是普通函數(shù)衷旅,只不過(guò)作用域鏈多了閉包Closure的成分
閉包的用途:1.模仿塊級(jí)作用域,在立即調(diào)用函數(shù)中聲明內(nèi)部需要使用的變量纵朋;2.管理私有變量和方法柿顶;3.函數(shù)柯里化
來(lái)個(gè)極端的例子
function closure() {
let result = [], count = 1
setInterval(() => {count++}, 1000)
function outer() {
let doufu = 'wu'
return function inner() {
let foo = 'foo'
return function closureCallback() {
let bar = {bar: 'bar'}, bar1 = 'bar'
result[i] = function () {
console.log(count)
let b = doufu
return bar.bar + i
}
}
}
}
let inner = outer()
let closureCallback = inner()
for (var i=0; i<10; i++) {
// [[scope]]包含closure{result, i}
// 函數(shù)在這里立即調(diào)用
// i為實(shí)時(shí)值0,1,2,3...
closureCallback()
}
return result
}
從圖中可以看到,最終返回結(jié)果分別引用了closureCallback, outer操软,closure中的變量嘁锯,所以,在作用域鏈中會(huì)保存這三個(gè)函數(shù)的活動(dòng)對(duì)象聂薪,不同時(shí)間調(diào)用家乘,返回的count值不同,說(shuō)明引用的是當(dāng)前值藏澳。
以下是手工示意圖
以下是我的學(xué)習(xí)過(guò)程
JavaScript里面的閉包仁锯,指的是函數(shù)與聲明該函數(shù)的詞法環(huán)境的組合。
一般在函數(shù)里面聲明函數(shù)翔悠,并且引用外面函數(shù)的變量业崖,就會(huì)產(chǎn)生閉包。定義在全局的時(shí)候蓄愁,默認(rèn)不產(chǎn)生閉包(所謂閉包双炕,就是當(dāng)內(nèi)部函數(shù)引用了外部函數(shù)中的變量時(shí),會(huì)在函數(shù)的作用域上面添加一層撮抓,這一層包含了函數(shù)所引用的外部函數(shù)的變量妇斤,存放在scope屬性里面,在調(diào)用時(shí)丹拯,用于形成作用域鏈)
函數(shù)在執(zhí)行時(shí)趟济,會(huì)在內(nèi)存中創(chuàng)建一個(gè)活動(dòng)對(duì)象,該活動(dòng)對(duì)象包含arguments以及一些參數(shù)咽笼。并通過(guò)復(fù)制[[scope]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈。
var bar = 'zoo'
function Foo() {
this.bar = bar
}
function foo() {
let bar = 'zoo'
function zoo() {
let zoo = bar
}
}
foo()
function closure() {
let result = []
for (var i=0; i<10; i++) {
result[i] = function () {
return i
}
}
return result
}
let arr = closure()
主要體現(xiàn)在函數(shù)返回函數(shù)戚炫,函數(shù)A在調(diào)用函數(shù)B時(shí)被創(chuàng)建并從B函數(shù)的內(nèi)部返回剑刑。當(dāng)我們調(diào)用A函數(shù)的時(shí)候,B函數(shù)的作用域鏈已經(jīng)從內(nèi)存中銷(xiāo)毀双肤,但是我們?nèi)匀豢梢栽贏中訪問(wèn)B中存在的變量施掏。因?yàn)锽中的變量仍然保存在A的活動(dòng)對(duì)象中(作用域鏈中[[scope]]對(duì)象里面)
此時(shí),函數(shù)A與A的scope構(gòu)成closure函數(shù)的閉包實(shí)例
從圖中可以看到茅糜,ar[0](以下稱(chēng)為函數(shù)A)函數(shù)的作用域鏈最頂層為自身活動(dòng)對(duì)象(arguments七芭, caller, length, name等等構(gòu)成)再往上則是由一個(gè)Closure對(duì)象實(shí)例構(gòu)成,可以看到這一層里面只包含一個(gè)變量i,即創(chuàng)建A時(shí)外層函數(shù)里面聲明的變量i蔑赘。當(dāng)我們調(diào)用A時(shí)狸驳,我們?cè)诘诙幼饔糜蜴溕厦嬲业降倪@個(gè)i變量预明。
為什么arr數(shù)組每一項(xiàng)都返回的是10,而不是對(duì)應(yīng)的下標(biāo)值的原因就在這里:當(dāng)我們調(diào)用數(shù)組項(xiàng)函數(shù)時(shí)耙箍,遇到變量i撰糠,并且在第二層作用域鏈讀取到i,這里面保存的i就是closure函數(shù)里面定義的i辩昆。在A調(diào)用時(shí)阅酪,closure已經(jīng)執(zhí)完畢,在closure執(zhí)行的過(guò)程中i的值從0變到了10汁针。這個(gè)性質(zhì)類(lèi)似于把原本存在于closure函數(shù)中的變量术辐,在closure函數(shù)執(zhí)行完畢后(從內(nèi)存中移除)我們可以在A自身的scope屬性里訪問(wèn)到。
簡(jiǎn)單一點(diǎn)說(shuō)就是我們?cè)谡{(diào)用A函數(shù)的時(shí)候施无,訪問(wèn)到的i變量辉词,是函數(shù)closure(雖然它不在了但是它的變量還在,仍然被scope屬性引用帆精。)的當(dāng)前值(實(shí)時(shí)值)
所以要達(dá)到預(yù)期目標(biāo)较屿,我們只需要保證它的scope對(duì)象中保存的這個(gè)‘i’值是正確的就可以了。
這里面的思路就是卓练,函數(shù)A被當(dāng)做普通函數(shù)調(diào)用時(shí)隘蝎,非閉包情況下,作用域就是自身(沒(méi)有i變量)+ 全局作用域(也沒(méi)有),所以這里還是需要借助閉包襟企。即需要保證A上一層作用域的i是正確的值
1.創(chuàng)建另外一個(gè)閉包,每個(gè)i的值都會(huì)創(chuàng)建一個(gè)閉包
function closureCallback(i) {
// 返回函數(shù)里面的i變量就是closureCallback函數(shù)的參數(shù)i
return function() {
return i
}
}
function closure() {
let result = [], b = 'closure'
for (var i=0; i<10; i++) {
// 這里的closureCallback作為普通函數(shù)調(diào)用
// 且沒(méi)有引用closure函數(shù)的變量嘱么,
// 函數(shù)作用域內(nèi)的變量,無(wú)法直接在函數(shù)外取得
// 所以作用域鏈不包含closure
// 所以在closureCallback函數(shù)[[scope]]中不會(huì)有closure函數(shù)
result[i] = closureCallback(i)
}
return result
}
2.使用匿名閉包
function closure() {
let result = [], b = 'closure'
for (var i=0; i<10; i++) {
result[i] = (function (i) {
return () => i
})(i)
}
return result
}
let arr = closure()
3.使用let顽悼,減少閉包曼振,let具有塊級(jí)作用域的效果
function closure() {
let result = [], b = 'closure'
for (var i=0; i<10; i++) {
let j = i
result[i] = function () {
return j
}
}
return result
}
let arr = closure()