每一個(gè)函數(shù)都有其所處的作用域戳杀,如果函數(shù)是在作用域之外(對(duì)于函數(shù)而言步做,作用域只跟聲明時(shí)所處的位置有關(guān),跟調(diào)用處無(wú)關(guān))執(zhí)行時(shí)酣胀,此時(shí)函數(shù)如果能夠記住并訪問(wèn)得到自己所在的作用域的話刁赦,那么此時(shí)便形成了一個(gè)閉包。
看一個(gè)簡(jiǎn)單例子:
function foo(){
var a = 2
function bar(){console.log(a)}//bar函數(shù)所處的作用域即foo函數(shù)內(nèi)部塊中
return bar
}
var baz = foo()
baz()//2
通過(guò)上面的例子結(jié)合自己實(shí)際經(jīng)驗(yàn)闻镶,你會(huì)發(fā)現(xiàn)閉包真的就是無(wú)處不在甚脉。下面分析一下:當(dāng)我們通過(guò)函數(shù)變量baz調(diào)用函數(shù)的時(shí)候,此時(shí)調(diào)用的便是內(nèi)存中的bar函數(shù)铆农。需要記住的是牺氨,盡管是不同的名字,但是對(duì)于淺拷貝來(lái)說(shuō),他們實(shí)際上引用的都是同一塊內(nèi)存中的數(shù)據(jù)猴凹,所以說(shuō)夷狰,這里的bar()和baz()是一樣的結(jié)果,問(wèn)題是閉包會(huì)產(chǎn)生這樣一種效果:阻止垃圾回收機(jī)制回收f(shuō)oo函數(shù)內(nèi)部塊中的某些數(shù)據(jù)郊霎,比如說(shuō)我們這里要用上的a以及bar函數(shù)本身沼头。對(duì)于函數(shù)而言,他的作用域和調(diào)用位置無(wú)關(guān)歹篓,所以說(shuō)關(guān)于閉包的核心點(diǎn)就是:對(duì)所需要用的資源的延遲回收瘫证。
下面來(lái)兩個(gè)復(fù)習(xí)作用域的例子:
var a = 3
function foo(){
var a = 2
function bar(){console.log(a)}
return bar
}
var baz = foo()
baz()//2
再看看下面這個(gè)例子
function handle(fun){
var test = 1
var num = fun()
return num+1
}
function dowork(){
var test = 6
var nm = handle(function ques(){console.log(test);return 1})//6
console.log(nm)//2
//var can = ques//ReferenceError,ques is not defined
}
dowork()
//輸出結(jié)果=>6 2
討論一下ques函數(shù)所處的作用域揉阎,猜測(cè)一:dowork塊庄撮;猜測(cè)二:handle塊。根據(jù)運(yùn)行結(jié)果很容易得知毙籽,肯定不是handle塊洞斯。那么是不是dowork塊?根據(jù)6這個(gè)輸出結(jié)果來(lái)看坑赡,我們可以得知可能是dowork塊烙如,但是既讓如此為什么在dowork塊里面不能訪問(wèn)得到ques函數(shù)呢?這又使得我們懷疑是否是dowork塊毅否。經(jīng)過(guò)思考發(fā)現(xiàn)亚铁,ques函數(shù)的作用域的確是dowork塊,那么為什么不能再dowork內(nèi)部訪問(wèn)ques呢螟加?答案是這樣的徘溢,先舉個(gè)例子:
var fun = function bar(){console.log(1)}
bar()//ReferenceError
就是上面的這種情況,一樣的道理捆探,但我們將一個(gè)函數(shù)聲明的代碼作為右值賦值的時(shí)候然爆,那么函數(shù)聲明中的那個(gè)名字會(huì)被編譯器給忽略。
1.循環(huán)與閉包
先看一個(gè)經(jīng)典例子:
for(var i = 0; i < 10; i++){
setTimeout(function(){console.log(i)}, i*1000)
}
分析:運(yùn)行的結(jié)果是——連續(xù)輸出了10個(gè)10黍图,每間隔1s輸出一次曾雕。但是,我們期望的可不是這樣的助被,我們希望能夠輸出0123456789剖张,那么問(wèn)題來(lái)了,為什么會(huì)導(dǎo)致這個(gè)問(wèn)題呢揩环?答案就是修械,setTimeout函數(shù)的特殊性,延時(shí)函數(shù)里面的回調(diào)函數(shù)會(huì)在循環(huán)結(jié)束時(shí)才執(zhí)行检盼。那么你設(shè)置的延時(shí)時(shí)間是0而不是1s肯污,所有的回調(diào)函數(shù)仍舊是在循環(huán)結(jié)束時(shí)才會(huì)執(zhí)行。而我們這里的回調(diào)函數(shù)的操作就是輸出變量i,由于是循環(huán)結(jié)束才會(huì)開始執(zhí)行回調(diào)函數(shù)蹦渣,所以他們都輸出了變量最后的那個(gè)值10哄芜。
要解決這個(gè)問(wèn)題有好多辦法,這里就介紹最簡(jiǎn)單的那個(gè)柬唯,利用let關(guān)鍵字:
for(let i = 0; i < 10; i++){
setTimeout(function(){console.log(i)}, i*1000)
}
當(dāng)for與let結(jié)合時(shí)认臊,let聲明的變量在每次循環(huán)都會(huì)被再次聲明。第n次聲明時(shí)所賦的值由第n-1次結(jié)束時(shí)的值來(lái)決定锄奢。