一、閉包
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
function foo(){
var a = 2;
function bar(){
console.log(a) // 2
}
bar()
}
上面的是閉包嗎?
確切的說并不是,在上面的代碼中,函數(shù)bar可以訪問foo作用域以及全局作用域,因?yàn)閎ar嵌套在函數(shù)內(nèi)部,但是這個并不是一個閉包。
function foo(){
var a = 2;
function bar(){
console.log(a) // 2
}
return bar()
}
var baz = foo();
baz(); // 2 這個才是閉包
函數(shù)bar() 的詞法作用域能夠訪問foo的內(nèi)部作用域,我們把函數(shù)bar()作為foo的返回值,在foo執(zhí)行之后,其返回值賦值給變量baz,調(diào)用baz,實(shí)際上就是通過不同的標(biāo)識符調(diào)用了內(nèi)部函數(shù)bar,bar可以被正常執(zhí)行。
在foo執(zhí)行之后,通常會期待foo的整個內(nèi)部作用域被銷毀,因?yàn)槲覀冎酪嬗欣厥掌饔脕磲尫挪辉偈褂玫膬?nèi)存空間,而閉包的神奇之處就在于阻止這樣的事情發(fā)生。因?yàn)閎ar本身還在使用foo內(nèi)部的變量,使得foo的作用域一直存在,一共bar在之后任何時間進(jìn)行引用。
bar依然持有該作用域的引用,而這個引用就叫做閉包。
即這個函數(shù)在定義時的詞法作用域以外的地方被調(diào)用,閉包使得函數(shù)可以繼續(xù)訪問定義時的詞法作用域。
function foo(){
var a = 2;
function bar(){
console.log(a) // 2
}
baz(bar)
}
function baz(fn){
fn() // 閉包
}
無論通過任何手段將內(nèi)部函數(shù)傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執(zhí)行這個函數(shù)都會使用閉包。
二、循環(huán)與閉包
for(var i = 1; i <= 5 ; i++){
setTimeout(function timer(){
console.log(i)
},i*1000)
}
這段代碼估計很多人面試的時候都見過,那你是怎么回答的呢?
延遲函數(shù)的回調(diào)在循環(huán)結(jié)束后才執(zhí)行。我們試圖假設(shè)循環(huán)中的每個迭代都會在運(yùn)行時候給自己‘捕獲’一個i的副本,但是根據(jù)作用域的工作原理,他們都在同一個作用域里,也就是說只有一個i,循環(huán)結(jié)束后是6,那么延遲函數(shù)執(zhí)行后獲取到的i就是6.
for(var i = 1; i <=5 ; i++){
(function(){
setTimeout(function timer(){
console.log(i)
},i*1000)
})()
}
這樣行嗎?
當(dāng)然不行,因?yàn)楝F(xiàn)在雖然后更多的詞法作用域,每個延遲函數(shù)都會將IIFE每次迭代中創(chuàng)建的作用域封閉起來,但是此時作用域中是空的,并沒有i,所以還是都打印出6。
for(var i = 1; i <=5 ; i++){
(function(i){
setTimeout(function timer(){
console.log(i)
},i*1000)
})(i)
}
這樣就可以了。問題解決。
其實(shí)閉包還有很多用圖,甚至經(jīng)常看到閉包,通過這篇文章,能夠認(rèn)識閉包就是一種進(jìn)步。