大多數(shù)面試官都會(huì)問你有關(guān)閉包是什么的問題同衣,而大多數(shù)時(shí)候你的一個(gè)錯(cuò)誤答案的代價(jià)就是失去一份工作。就算你夠幸運(yùn)的拿到了這份工作的 offer辛孵,你也會(huì)在年薪上無形損失上萬(wàn)美元厨疙。因?yàn)槟銜?huì)以初級(jí)開發(fā)工程師的身份被招進(jìn)公司,你的工作經(jīng)驗(yàn)有多久人家是不會(huì)在乎的轩娶。(掘金文章里說的)
(本文純屬個(gè)人學(xué)習(xí)記錄)
一儿奶、變量的作用域
要理解閉包,首先必須理解Javascript特殊的變量作用域鳄抒。
變量的作用域無非就是兩種:全局變量和局部變量闯捎。
Javascript語(yǔ)言的特殊之處搅窿,就在于函數(shù)內(nèi)部可以直接讀取全局變量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
另一方面隙券,在函數(shù)外部自然無法讀取函數(shù)內(nèi)的局部變量男应。
function f1(){
var n=999;
}
alert(n); // error
這里有一個(gè)地方需要注意,函數(shù)內(nèi)部聲明變量的時(shí)候娱仔,一定要使用var命令沐飘。如果不用的話,你實(shí)際上聲明了一個(gè)全局變量牲迫!
function f1(){
n=999;
}
f1();
alert(n); // 999
二耐朴、如何從外部讀取局部變量?
出于種種原因盹憎,我們有時(shí)候需要得到函數(shù)內(nèi)的局部變量筛峭。但是,前面已經(jīng)說過了陪每,正常情況下影晓,這是辦不到的,只有通過變通方法才能實(shí)現(xiàn)檩禾。
那就是在函數(shù)的內(nèi)部挂签,再定義一個(gè)函數(shù)。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
在上面的代碼中盼产,函數(shù)f2就被包括在函數(shù)f1內(nèi)部饵婆,這時(shí)f1內(nèi)部的所有局部變量,對(duì)f2都是可見的戏售。但是反過來就不行侨核,f2內(nèi)部的局部變量,對(duì)f1就是不可見的灌灾。這就是Javascript語(yǔ)言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope)搓译,子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。所以紧卒,父對(duì)象的所有變量侥衬,對(duì)子對(duì)象都是可見的,反之則不成立跑芳。
既然f2可以讀取f1中的局部變量轴总,那么只要把f2作為返回值,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎博个!
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
三怀樟、什么是閉包
閉包即函數(shù)與其引用的周邊狀態(tài)(詞法環(huán)境)綁定在一起形成的(封裝)組合。換句話說盆佣,閉包可以讓我們從函數(shù)內(nèi)部訪問其外部函數(shù)的作用域往堡。在 JavaScript 中械荷,每當(dāng)函數(shù)創(chuàng)建,閉包就被創(chuàng)建虑灰。
為了使用閉包吨瞎,我們可以簡(jiǎn)單的將一個(gè)函數(shù)定義在另一個(gè)函數(shù)的內(nèi)部,然后將其暴露給外部穆咐,返回這個(gè)函數(shù)或者是把它傳給另一個(gè)函數(shù)颤诀。
內(nèi)部函數(shù)會(huì)擁有訪問外部函數(shù)作用域中變量的能力,即使是外部函數(shù)已經(jīng)執(zhí)行完畢并銷毀对湃。
(在本質(zhì)上崖叫,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁)
簡(jiǎn)單最原始的閉包,以便讓你在大腦里產(chǎn)生閉包的畫面:
function A() {
function B() {
console.log('Hello 閉包!');
}
return B;
}
var C = A();
C(); // Hello 閉包!
這是最簡(jiǎn)單的閉包拍柒。
有了初步認(rèn)識(shí)后心傀,我們簡(jiǎn)單分析一下它和普通函數(shù)有什么不同,上面代碼翻譯成自然語(yǔ)言如下:
定義普通函數(shù) A
在 A 中定義普通函數(shù) B
在 A 中返回 B
執(zhí)行 A拆讯,并把 A 的返回結(jié)果賦值給變量 C
執(zhí)行 C
把這5步操作總結(jié)成一句話就是:
函數(shù)A的內(nèi)部函數(shù)B被函數(shù)A外的一個(gè)變量 c 引用脂男。
把這句話再加工一下就變成了閉包的定義:
當(dāng)一個(gè)內(nèi)部函數(shù)被其外部函數(shù)之外的變量引用時(shí),就形成了一個(gè)閉包往果。
因此疆液,當(dāng)你執(zhí)行上述5步操作時(shí),就已經(jīng)定義了一個(gè)閉包陕贮!
這就是閉包。
四潘飘、使用閉包
閉包可以用在許多地方肮之。它的最大用處有兩個(gè),一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量卜录,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中戈擒。
在了解閉包的作用之前,我們先了解一下 Javascript 中的 GC 機(jī)制:
在 Javascript 中艰毒,如果一個(gè)對(duì)象不再被引用筐高,那么這個(gè)對(duì)象就會(huì)被 GC 回收,否則這個(gè)對(duì)象一直會(huì)保存在內(nèi)存中丑瞧。
在上述例子中柑土,B 定義在 A 中,因此 B 依賴于 A ,而外部變量 C 又引用了 B , 所以A間接的被 C 引用绊汹。
也就是說稽屏,A 不會(huì)被 GC 回收,會(huì)一直保存在內(nèi)存中西乖。為了證明我們的推理狐榔,上面的例子稍作改進(jìn):
function A() {
var count = 0;
function B() {
count ++;
console.log(count);
}
return B;
}
var C = A();
C();// 1
C();// 2
C();// 3
count 是函數(shù)A 中的一個(gè)變量坛增,它的值在函數(shù)B 中被改變,函數(shù) B 每執(zhí)行一次薄腻,count 的值就在原來的基礎(chǔ)上累加 1 收捣。因此,函數(shù)A中的 count 變量會(huì)一直保存在內(nèi)存中庵楷。
當(dāng)我們需要在模塊中定義一些變量罢艾,并希望這些變量一直保存在內(nèi)存中但又不會(huì) “污染” 全局的變量時(shí),就可以用閉包來定義這個(gè)模塊嫁乘。
五昆婿、另外
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
return function () {
return this.name;
};
}
}
alert(object.getNameFunc()()); //The Window
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function () {
var that = this;
return function () {
return that.name;
};
}
};
alert(object.getNameFunc()()); //My Object
因?yàn)榈谝活}中g(shù)etNameFunc這個(gè)方法或者叫函數(shù)是屬于全局作用域的,所以里面返回的this始終都是指向window的蜓斧。
而第二題中用that=this改變了當(dāng)前函數(shù)指向的作用域仓蛆,所以第二題中的this最終只想的是myobject。
六挎春、使用閉包的注意點(diǎn)
1)由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中看疙,內(nèi)存消耗很大,所以不能濫用閉包直奋,否則會(huì)造成網(wǎng)頁(yè)的性能問題能庆,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是脚线,在退出函數(shù)之前搁胆,將不使用的局部變量全部刪除。
2)閉包會(huì)在父函數(shù)外部邮绿,改變父函數(shù)內(nèi)部變量的值渠旁。所以,如果你把父函數(shù)當(dāng)作對(duì)象(object)使用船逮,把閉包當(dāng)作它的公用方法(Public Method)顾腊,把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時(shí)一定要小心挖胃,不要隨便改變父函數(shù)內(nèi)部變量的值杂靶。
參考:
https://juejin.im/post/5cfd11fbe51d4555fd20a30d
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://www.cnblogs.com/onepixel/p/5062456.html