之前看過(guò)一些關(guān)于閉包的文章哼转,當(dāng)時(shí)對(duì)閉包的理解還是不夠透徹,前些天又回顧了《JavaScript高級(jí)程序設(shè)計(jì)第三版》中的閉包章節(jié),對(duì)閉包有了新的理解抖坪。
初識(shí)閉包
閉包 實(shí)際上是通過(guò)調(diào)用一個(gè)函數(shù)有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量。切記 閉包 不是 匿名函數(shù)闷叉,要搞清楚概念擦俐。但在閉包中會(huì)經(jīng)常使用匿名函數(shù)。
首先來(lái)看一個(gè)最簡(jiǎn)單的閉包案例片习,讓你對(duì)閉包有一個(gè)簡(jiǎn)單的認(rèn)識(shí)捌肴,畢竟入門(mén)第一個(gè)案例,所以不涉及匿名函數(shù)藕咏,也不涉及函數(shù)表達(dá)式立即執(zhí)行等高級(jí)玩意兒状知。
function A() {
var name = '閉包';
function B() {
console.log("Hello " + name + "!");
}
return B;
}
var C = A();
C();
這是一個(gè)不能再簡(jiǎn)單了的閉包案例,以下是代碼分析孽查。
- 定義了一個(gè)普通函數(shù)A
- 在A中定義了一個(gè)name變量饥悴、一個(gè)函數(shù)B
- 在A中返回一個(gè)B的引用(因?yàn)閒unction是引用類(lèi)型)
- 執(zhí)行A,返回B的引用賦值給C
- 執(zhí)行C(此時(shí)C引用的是B)
- 結(jié)果為 Hello 閉包盲再!
從上例中可以看出西设,外部C引用了內(nèi)部函數(shù)B,可以訪問(wèn)函數(shù)A作用域內(nèi)的所有變量答朋。
這也是閉包的基本概念:外部作用域通過(guò)函數(shù)內(nèi)部返回引用贷揽,有權(quán)訪問(wèn)該函數(shù)作用域中的變量。
不需要死記概念梦碗,如果你完成了上述的前5步禽绪,已經(jīng)形成了一個(gè)閉包。
垃圾回收
一般情況下洪规,當(dāng)函數(shù)執(zhí)行完畢后印屁,局部活動(dòng)對(duì)象就會(huì)被銷(xiāo)毀,也就是所謂的垃圾回收機(jī)制斩例,內(nèi)存里面只存有全局變量雄人。但閉包則不一樣,因?yàn)殚]包通常返回了一個(gè)引用念赶,由于該引用沒(méi)有解除础钠,所以局部活動(dòng)對(duì)象仍然留在內(nèi)存中恰力。
為了證實(shí)在閉包情況下不解除引用,局部活動(dòng)對(duì)象不會(huì)被垃圾回收珍坊,在第一個(gè)例子中稍加改進(jìn)后如下:
function A() {
var count = 0;
function B() {
console.log(++count);
}
return B;
}
var C = A();
C(); // 1
C(); // 2
C(); // 3
C = null; // 解除引用
C =A();
C(); // 1
- 在A中定義了一個(gè) count 變量牺勾、和函數(shù)B
- 在B中對(duì) count 做自增運(yùn)算并輸出控制臺(tái)
- 在A中返回函數(shù)B的引用
- 執(zhí)行函數(shù)A,并把返回的結(jié)果賦值給C
- 執(zhí)行3次C
- 解除變量C的引用
- 再次執(zhí)行函數(shù)A阵漏,并把返回的結(jié)果賦值給C
- 執(zhí)行1次C
通過(guò)上面的例子可以發(fā)現(xiàn)驻民,如果不解除C的引用,函數(shù)A中的count一直在內(nèi)存中履怯,但count卻不是全局變量回还。當(dāng)不想污染全局變量卻需要使變量一直停留在內(nèi)存以便使用的時(shí)候,可以使用閉包叹洲,這也是閉包有優(yōu)點(diǎn)柠硕。在使用閉包過(guò)程中一定要注意內(nèi)存泄露,當(dāng)該變量不在有使用價(jià)值的時(shí)候运提,立刻解除引用蝗柔。
閉包寫(xiě)法
在jQuery、zepto等一系列框架中民泵,閉包都是配合匿名函數(shù)癣丧,以及函數(shù)自執(zhí)行來(lái)配合使用的。所以前面也強(qiáng)調(diào)過(guò) 閉包不是匿名函數(shù)栈妆,匿名函數(shù)也不是閉包胁编。所以在開(kāi)講之前,必須要弄清楚什么叫函數(shù)申明鳞尔,什么叫函數(shù)表達(dá)式嬉橙。
函數(shù)聲明
以下就是一個(gè)最簡(jiǎn)單的函數(shù)聲明:
set(); // hello
function set() {
console.log('hello');
}
函數(shù)申明有一個(gè)特征就是函數(shù)申明提升,在執(zhí)行代碼之前會(huì)先讀取函數(shù)申明寥假,這意味著函數(shù)的申明可以放在調(diào)用它的語(yǔ)句后面市框。
函數(shù)表達(dá)式
以下就是一個(gè)最簡(jiǎn)單的函數(shù)聲明:
set(); // 報(bào)錯(cuò),函數(shù)不存在
var set = function () {
console.log('hello');
}
函數(shù)表達(dá)式因?yàn)槭潜磉_(dá)式糕韧,所以必須一行一行的讀取拾给,所以會(huì)報(bào)錯(cuò)。
在這里不討論它們的先后順序錯(cuò)誤兔沃,只需要感受一下函數(shù)申明與函數(shù)表達(dá)式的區(qū)別。
弄清楚函數(shù)表達(dá)式基本寫(xiě)法级及,那么可以下面這個(gè)例子乒疏。
var set = function (n) {
console.log('hello' + n);
}
set('Riny'); // 結(jié)果為 helloRiny
/* ---------------- */
(function() {
console.log('hello');
})('Riny'); // 結(jié)果為 helloRiny
上面的第二種寫(xiě)法是申明一個(gè)匿名函數(shù),為匿名函數(shù)前后添加括號(hào)饮焦,轉(zhuǎn)換為表達(dá)式怕吴,然后在表達(dá)式的后面?zhèn)鲄?zhí)行窍侧。為此有一個(gè)專(zhuān)門(mén)的叫法:立即調(diào)用的函數(shù)表達(dá)式(IIFE),這里必須要理解括號(hào)的作用转绷。
// 函數(shù)表達(dá)式
(function () { /* 函數(shù)體 */ });
// 函數(shù)聲明
function () { /* 函數(shù)體 */ }
// 立即調(diào)用的函數(shù)表達(dá)式
(function () { /* 函數(shù)體 */ })();
下面看一個(gè)真正的閉包案例:
(function (doc) {
var viewport;
function init(id) {
viewport = doc.querySelector("#"+id);
}
function addChild(child) {
viewport.appendChild(child);
}
function removeChild(child) {
viewport.removeChild(child);
}
window.operateElem = {
init: init,
addChild: addChild,
removeChild: removeChild
};
})(document);
- 定義了一個(gè)立即調(diào)用的函數(shù)表達(dá)式(IIFE)伟件,執(zhí)行時(shí)傳遞一個(gè)參數(shù)document
- 在函數(shù)表達(dá)式內(nèi)部,定義了一個(gè)變量viewport议经、和三個(gè)函數(shù)
- 通過(guò)全局變量把內(nèi)部的三個(gè)函數(shù)斧账,以對(duì)象的形式返回給 **window.operateElem **
- 與此同時(shí)三個(gè)函數(shù)共享一個(gè)viewport變量
這里沒(méi)有使用 ruturn 語(yǔ)句,而是直接把要返回的值賦值給全局變量window.operateElem煞肾。這樣就可以使用這些方法oprateElem.init()咧织,oprateElem.addChild(),oprateElem.removeChild()籍救。從而減少了全局變量污染习绢。只有解除掉引用,立即調(diào)用的函數(shù)表達(dá)式的活動(dòng)對(duì)象才會(huì)從內(nèi)存中銷(xiāo)毀蝙昙。
結(jié)尾
閉包的知識(shí)點(diǎn)還不知這些闪萄,比如匿名函數(shù)的this問(wèn)題,執(zhí)行環(huán)境奇颠、活動(dòng)對(duì)象败去、作用域鏈等知識(shí),所以想要深刻理解閉包大刊,一定要系統(tǒng)化的去學(xué)習(xí)JavaScript为迈,這樣才能有助于你更深層次的理解。本篇內(nèi)容到這里就結(jié)束了缺菌,趕緊寫(xiě)個(gè)Demo試試吧葫辐。