閉包
閉包定義:指擁有多個(gè)變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分矾削。
函數(shù)內(nèi)部可以直接讀取全局變量侦厚。
函數(shù)內(nèi)部變量無(wú)法在函數(shù)外部訪問(wèn)健蕊。
函數(shù)內(nèi)部聲明要用var或者let聲明,不然會(huì)變成全局變量
鏈?zhǔn)阶饔糜颍鹤訉?duì)象會(huì)一級(jí)級(jí)向上尋找父對(duì)象的變量,父對(duì)象的變量子對(duì)象都是可見(jiàn)的缰猴,反之則不行吗购。
在一個(gè)閉包環(huán)境內(nèi)修改變量值,不會(huì)影響另一個(gè)閉包中的變量冰垄。
普通的函數(shù)內(nèi)嵌蹬癌,內(nèi)部函數(shù)是先執(zhí)行;而閉包則是:先把內(nèi)部函數(shù)賦給外部函數(shù)虹茶,然后在執(zhí)行逝薪。
下面這段代碼就是一根典型的閉包
function f1(){
var a = 10;
function f2(){
alert(a);
}
f2(); //①
}
f1(); //10 ②
f1
和f1()
的區(qū)別不加括號(hào)是代碼,加()
是執(zhí)行這段代碼蝴罪,加return
是返回一個(gè)值董济,可以把返回的值賦值給變量,不加return
默認(rèn)返回undefined
要门;
所以①
處有三種寫法:
第一種:①
處寫f2();
虏肾,②
處調(diào)用需要這樣寫f1();
。具體執(zhí)行過(guò)程:f1
體內(nèi)調(diào)用f2
函數(shù)欢搜,并執(zhí)行封豪。
第二種:①
處寫return f2();
,②
處調(diào)用需這樣寫f1();
炒瘟。具體執(zhí)行過(guò)程:同上吹埠;區(qū)別是多了個(gè)return
,因?yàn)楝F(xiàn)在f2
函數(shù)中沒(méi)有返回值疮装,所以f1
在調(diào)用f2
只是執(zhí)行一下alert(a)
缘琅,f1
的返回值是undefined
。
第三種:①
處寫return f2;
廓推,②
處調(diào)用需這樣寫f1()();
胯杭。這里返回的是f2
函數(shù)的代碼,所以在調(diào)用f1
時(shí)要加上2個(gè)括號(hào)受啥,第一個(gè)括號(hào)是執(zhí)行f1
函數(shù)做个,第2個(gè)括號(hào)是執(zhí)行f2
函數(shù)鸽心,如果①
處省略return
會(huì)報(bào)錯(cuò)。
return
和函數(shù)調(diào)用時(shí)是否加括號(hào)的意思都明白居暖,但是把它倆結(jié)合起來(lái)顽频,就搞不清了。
正好今天學(xué)閉包時(shí)碰上了太闺,順便就把它搞清楚了糯景。
到底什么是閉包
對(duì)于新人(當(dāng)然了是說(shuō)我了),看很多閉包的定義省骂,代碼蟀淮,還是不知啥是閉包,云里霧里的钞澳,這里感謝方方老師的文章JS 中的閉包是什么怠惶?,看完后轧粟,雖然還是說(shuō)不出啥是閉包策治,但現(xiàn)在已經(jīng)知道啥是閉包了,果然用圖說(shuō)話最牛逼兰吟。(圖在文章中通惫,我就不放出來(lái)了)
閉包的應(yīng)用
MDN 上這個(gè)例子也寫的很好
調(diào)用
Counter.value()
時(shí),返回的是Counter
內(nèi)部的變量privateCounter
混蔼;increment
內(nèi)部沒(méi)有返回值履腋,這個(gè)方法只是執(zhí)行了privateCounter + 1
操作,沒(méi)有返回值惭嚣;同理
decrement
是將privateCounter - 1
府树,也沒(méi)有返回值;所以執(zhí)行
Counter.increment
料按,會(huì)返回undefined
奄侠,但是接著操作Counter.value()
時(shí)就可以得到1
,因?yàn)閳?zhí)行上一步Counter.increment
時(shí)privateCounter
被+1
了载矿。
今天在下面三段代碼上花費(fèi)了大量的時(shí)間垄潮,一直似懂非懂,心里不踏實(shí)闷盔。
代碼一:
var name = 'window';
var obj = {
name: 'object',
getName: function() {
return this.name;
}
};
obj.getName(); //object
(obj.getName = obj.getName)(); //window 非嚴(yán)格模式下
代碼二:
var name = 'window';
var obj = {
name: 'object',
getName: function() {
return function(){
return this.name;
}
}
};
obj.getName()(); //window
代碼三:
var name = 'window';
var obj = {
name : 'object',
getName : function(){
var that = this;
return function(){
return that.name;
};
}
};
obj.getName()(); //object
今天在看阮一峰的博客學(xué)習(xí)Javascript閉包(Closure)弯洗,對(duì)代碼二、代碼三部分很是不解逢勾,看到一網(wǎng)友搬出犀牛書(還沒(méi)看過(guò)牡整,我買了紅寶石書才看了一點(diǎn)點(diǎn))里的話,實(shí)在不解什么是作為函數(shù)調(diào)用溺拱,什么是作為方法調(diào)用逃贝;
《Javascript權(quán)威指南》上說(shuō):如果嵌套函數(shù)作為函數(shù)調(diào)用谣辞,其this值不是全局對(duì)象(非嚴(yán)格模式下)就是undefined(嚴(yán)格模式下); 如果嵌套函數(shù)作為方法調(diào)用,其this值指向調(diào)用它的對(duì)象沐扳。
又有一位網(wǎng)友說(shuō)
每個(gè)函數(shù)在被調(diào)用時(shí)泥从,其活動(dòng)對(duì)象都會(huì)自動(dòng)取得兩個(gè)特殊變量:this和arguments。內(nèi)部函數(shù)在搜索這個(gè)變量時(shí)沪摄,只會(huì)搜索到其活動(dòng)對(duì)象為止躯嫉,因此永遠(yuǎn)不可能直接訪問(wèn)外部函數(shù)中的這兩個(gè)變量(這一點(diǎn)通過(guò)前面的圖可以看得更清楚)。意思就是說(shuō)找到匿名函數(shù)中的this和arguments就不會(huì)再往下找了(這里的往下指的是外層的包含函數(shù)杨拐,和最外層的window全局環(huán)境)祈餐,而匿名函數(shù)的this對(duì)象通常指向window,所以輸出的是全局的那個(gè)字符串哄陶。不過(guò)帆阳,把外部作用域中的this對(duì)象保存在一個(gè)閉包能夠訪問(wèn)到的變量里,就可以讓閉包訪問(wèn)該對(duì)象了奕筐。
看到這里大概明白匿名函數(shù)的作用域是全局,繼續(xù)翻看下面評(píng)論变骡,大概意思是說(shuō)“把this
保存在obj
作用域下的一個(gè)變量中离赫,this
就在當(dāng)前函數(shù)的作用域下了”。直到看完也是塌碌,似懂非懂渊胸,反正就是感覺(jué)哪里不對(duì)勁,但也說(shuō)不上了台妆。
直到看到【JavaScript】【函數(shù)】閉包閉包翎猛!這篇文章的代碼一部分,終于明白其中的邏輯了接剩。
下面就來(lái)分析其中的邏輯切厘,我分析的方法就是把不懂的地方一個(gè)個(gè)用console
打印出來(lái)
代碼二和代碼一的區(qū)別是多了一層嵌套函數(shù),this
值就不一樣了懊缺。
ps:我一開(kāi)始以為在代碼二中再嵌套一層函數(shù)疫稿,就會(huì)打印出object
=_=|||
先來(lái)看代碼一,明白之后鹃两,另外兩段代碼自然就懂了遗座。
為什么obj.getName()
打印出來(lái)的是object
,因?yàn)檫@時(shí)getName
方法是在obj
的作用域下俊扳,所以this
指向obj
途蒋,返回值當(dāng)然就是object
了。
接著看(obj.getName = obj.getName)()
刪掉右邊后馋记,打印出的結(jié)果變成了object
号坡,這就納悶了懊烤。
ps:第一眼看上去,這啥玩意筋帖,把自己賦值給自己奸晴?這不是多此一舉,直接用不就行了日麸!
用console.log
打印出obj.getName
后寄啼,終于撥云見(jiàn)天,obj.getName = obj.getName
這句話的意思就是把getName
函數(shù)賦值給自己代箭,這個(gè)時(shí)候就不是obj.getName
墩划,而是getName
匿名函數(shù)了,匿名函數(shù)通常用的方法是()()
立即執(zhí)行嗡综,此時(shí)再看匿名函數(shù)已經(jīng)脫離obj
了乙帮,當(dāng)然this
也就指向了全局,打印出window
极景。
再來(lái)看代碼二察净,用console
打印出obj.getName()
會(huì)發(fā)現(xiàn)是一個(gè)匿名函數(shù),而匿名函數(shù)的this通常會(huì)指向全局盼樟,所以也就不難理解了
理解上面兩段代碼氢卡,代碼三也就很好理解了。
閉包中引用循環(huán)變量
廖雪峰的閉包在文中就很形象的講解了函數(shù)中的引用會(huì)變化的變量會(huì)有什么后果晨缴,我節(jié)選了他的結(jié)論和代碼译秦。
返回閉包時(shí)牢記的一點(diǎn)就是:返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變量击碗。
如果一定要引用循環(huán)變量怎么辦筑悴?方法是再創(chuàng)建一個(gè)函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值稍途,無(wú)論該循環(huán)變量后續(xù)如何更改阁吝,已綁定到函數(shù)參數(shù)的值不變:
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}
var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); // 1
f2(); // 4
f3(); // 9
這里的核心就是立即執(zhí)行,如果不是立即執(zhí)行的話械拍,變量i
就是for
循環(huán)結(jié)束后的值了求摇。