如果你學(xué)JavaScript已經(jīng)有一段時(shí)間了,你一定聽(tīng)過(guò)js閉包。這也是前端面試非常喜歡問(wèn)的問(wèn)題呼股,所以理解js閉包對(duì)學(xué)習(xí)JavaScript至關(guān)重要。我曾經(jīng)多次理解js閉包画恰,但是每次我覺(jué)得我理解的時(shí)候彭谁,都會(huì)被之后的某些坑證明我根本沒(méi)有理解,最近在學(xué)習(xí)vue.js,所以想繼續(xù)扎實(shí)下原生js阐枣,又遇到了閉包马靠,所以借此機(jī)會(huì)總結(jié)一下對(duì)js閉包的理解。
初識(shí)閉包
我并不是從教材開(kāi)始JavaScript的學(xué)習(xí)的蔼两,所以一開(kāi)始并不知道有閉包這一概念甩鳄。而之所以開(kāi)始了解閉包是因?yàn)閿]碼中一個(gè)很常見(jiàn)的場(chǎng)景:如給列表的每一項(xiàng)都加一個(gè)根據(jù)index的事件,所以很自然的我們會(huì)這么寫(xiě)
for(var i=0;i<items.length;i++){
items[i].onclick=function(){
console.log(i);
}
}
然而额划,我發(fā)現(xiàn)結(jié)果并非我想象的那樣妙啃,i永遠(yuǎn)都是items.length,然后我問(wèn)別人也好,百度也罷俊戳,總是聽(tīng)到見(jiàn)到閉包這個(gè)詞揖赴,然后開(kāi)始去理解它。
理解閉包
1.閉包概念(出自JavaScript高級(jí)程序設(shè)計(jì))閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)
主謂賓:閉包是函數(shù)
定(什么樣的函數(shù)):有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù)
2.理解閉包所需的知識(shí)
1.JavaScript語(yǔ)言的作用域(ES6之前)沒(méi)有塊級(jí)作用域抑胎,只有全局作用域與函數(shù)作用域燥滑。
2.執(zhí)行環(huán)境、變量對(duì)象阿逃、活動(dòng)對(duì)象铭拧、作用域鏈
執(zhí)行環(huán)境:可以理解為作用域,執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問(wèn)的其他數(shù)據(jù)恃锉,決定了它們各自的行為搀菩。
變量對(duì)象:每一個(gè)執(zhí)行環(huán)境都有一個(gè)關(guān)聯(lián)的變量對(duì)象,用于保存該環(huán)境中定義的所有變量和函數(shù)破托。例如與全局執(zhí)行環(huán)境關(guān)聯(lián)的便是全局變量對(duì)象肪跋,所有全局變量與方法都會(huì)添加到該對(duì)象上。如在瀏覽器環(huán)境下土砂,全局對(duì)象即window對(duì)象州既。
活動(dòng)對(duì)象:當(dāng)執(zhí)行環(huán)境是函數(shù)時(shí)谜洽,所關(guān)聯(lián)的變量對(duì)象。
作用域鏈:顧名思義易桃,即多個(gè)作用域形成的鏈條褥琐。當(dāng)代碼在環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈晤郑,用于保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)敌呈。
看概念可能和容易糊涂,但通過(guò)代碼很好理解造寝。
如
var color="red";
function changeColor(){
var anotherColor="blue";
function swapColor(){
var tempColor="yellow";
}
}
上述代碼涉及三個(gè)執(zhí)行環(huán)境磕洪,全局執(zhí)行環(huán)境,changeColor()的執(zhí)行環(huán)境诫龙,swapColor的執(zhí)行環(huán)境析显,但它們之間不是孤立的,而是有關(guān)系的签赃,他們之間相互作用形成了相應(yīng)的作用域鏈谷异。在swaColor()執(zhí)行時(shí)候,它的作用域鏈上有三個(gè)變量對(duì)象锦聊,所以它可以訪問(wèn)所有的變量與函數(shù)歹嘹,但是changeColor執(zhí)行時(shí),所形成的作用域鏈上只有它自己的活動(dòng)對(duì)象與全局對(duì)象孔庭,所以它只能訪問(wèn)自己的變量尺上,函數(shù)和全局變量與函數(shù)。同理圆到,全局執(zhí)行環(huán)境下怎抛,只可以訪問(wèn)全局變量與對(duì)象。
**變量訪問(wèn)是沿著作用域鏈從前到后(全局)進(jìn)行訪問(wèn)的 **
3.js函數(shù)被調(diào)用的細(xì)節(jié)
函數(shù)被調(diào)用時(shí)芽淡,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境及相應(yīng)的作用域鏈马绝,然后使用arguments和其他命名參數(shù)初始化活動(dòng)對(duì)象。
4.this
在全局函數(shù)中挣菲,this對(duì)象等于window對(duì)象
在函數(shù)作為某對(duì)象方法時(shí)富稻。this等于該對(duì)象
匿名函數(shù)具有全局性
this和arguments是倆特殊的變量,內(nèi)部函數(shù)在搜索這倆個(gè)變量時(shí)己单,只會(huì)搜索到自己的活動(dòng)對(duì)象唉窃。因此永遠(yuǎn)也不可能直接訪問(wèn)外部函數(shù)的這倆變量耙饰。
5.立即執(zhí)行函數(shù)
第一次接觸覺(jué)得還挺玄乎的一個(gè)東西纹笼,而懂了原理只好,就沒(méi)什么東西了苟跪。
函數(shù)定義:function fun1(){}
函數(shù)調(diào)用:fun1();
立即執(zhí)行函數(shù)即將二者結(jié)合起來(lái)廷痘,但是function fun1(){}() 會(huì)報(bào)錯(cuò)蔓涧,因?yàn)槎x無(wú)法調(diào)用,但是表達(dá)式可以笋额,于是上面可以通過(guò)下面這倆種常見(jiàn)的方式結(jié)合
(function fun1(){})() 或者
(function fun1(){}())
6.js函數(shù)傳參是按值傳遞
閉包有何用元暴?怎么用?
1.先來(lái)解釋?zhuān)_(kāi)頭的那段代碼兄猩。茉盏、
i之所以沒(méi)有按照0,1枢冤,2...的順序打印鸠姨,是因?yàn)镴avaScript沒(méi)有塊級(jí)作用域
,所以不同item在調(diào)用綁定的函數(shù)時(shí)淹真,訪問(wèn)的都是同一個(gè)i,而這個(gè)i此時(shí)早已變成了10. 那么如何用閉包實(shí)現(xiàn)我們想要的效果呢讶迁?
模仿一個(gè)塊級(jí)作用域嘍
for(var i=0;i<items.length;i++){
items[i].onclick=(function(num){
return function(){
console.log(num);
}
})(i);
}
這個(gè)return回去的匿名函數(shù),正是我們所創(chuàng)建的閉包核蘸,它可以訪問(wèn)它的上一層作用域中的變量num,而因?yàn)楹瘮?shù)傳參是按值傳參的巍糯,所以針對(duì)每一個(gè)i,在匿名函數(shù)執(zhí)行時(shí),i都會(huì)形成一個(gè)相應(yīng)的副本客扎,而因?yàn)殚]包內(nèi)保持著對(duì)外部變量num的引用祟峦,所以雖然閉包的外部函數(shù)(即匿名立即執(zhí)行函數(shù))已經(jīng)執(zhí)行完畢,但是其作用域鏈仍然在引用該立即執(zhí)行函數(shù)的活動(dòng)對(duì)象虐唠,盡管此時(shí)它的執(zhí)行環(huán)境已經(jīng)被銷(xiāo)毀搀愧。
所以閉包的一個(gè)重要作用就是,讓某些變量的值始終保持在內(nèi)存中疆偿,即模仿塊級(jí)作用域
閉包的另一個(gè)作用就是使得外部作用域可以訪問(wèn)內(nèi)部作用域的變量(根據(jù)上面所講的作用域的知識(shí)咱筛,我們知道默認(rèn)情況下,這是不可能的)但是閉包可以實(shí)現(xiàn)
function fun1(){
var n=999;
function fun2(){
console.log(n);
}
return fun2;
}
var r=fun1();
r();
如此杆故,外部便訪問(wèn)到了內(nèi)部變量
注意事項(xiàng)
我們利用閉包模仿塊級(jí)作用域迅箩,將某些變量保存在內(nèi)存中,但也因此會(huì)給我們帶來(lái)一些問(wèn)題处铛。大量使用閉包會(huì)使很多變量保存在內(nèi)存中饲趋,影響網(wǎng)頁(yè)性能,所以使用時(shí)撤蟆,對(duì)不用的局部變量奕塑,要記得手動(dòng)刪除
閉包中使用this
上面我已經(jīng)說(shuō)過(guò),this與arguments的特殊之處家肯,即每個(gè)函數(shù)都會(huì)有自己的this和arguments龄砰,所以不可能直接訪問(wèn)到父作用域的this
var name="the window";
var obj={
name:" the object",
sayName:function(){
var that=this; //先將this保存到閉包可以訪問(wèn)的地方,
return function(){
return that.name;
}
}
}