譯者按: 在上一篇博客,我們通過實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,了解了如何使用閉包(Closure)**,這篇博客將提供一些代碼示例,幫助大家理解閉包。
成都創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括濱江網(wǎng)站建設(shè)、濱江網(wǎng)站制作、濱江網(wǎng)頁制作以及濱江網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,濱江網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到濱江省份的部分城市,未來相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
原文: JavaScript Closures for Dummies
為了保證可讀性,本文采用意譯而非直譯。另外,本文版權(quán)歸原作者所有,翻譯僅用于學(xué)習(xí)。
其實(shí),只要你領(lǐng)會(huì)了閉包的關(guān)鍵概念,一切就非常簡單了。作為JavaScript開發(fā)者,你應(yīng)該可以理解以下代碼:
function sayHello(name)
{
var text = 'Hello ' + name;
var sayAlert = function() { console.log(text); }
sayAlert();
}
sayHello("Bob") // 輸出"Hello Bob"
在sayHello()函數(shù)中定義并調(diào)用了sayAlert()函數(shù);sayAlert()作為內(nèi)層函數(shù),可以訪問外層函數(shù)sayHello()中的text變量。理解這一點(diǎn),你就可以繼續(xù)閱讀這篇博客了。
兩句話總結(jié)閉包(注意,這個(gè)定義并不規(guī)范,但是有助于理解):
function sayHello2(name)
{
var text = 'Hello ' + name; // 局部變量
var sayAlert = function() { console.log(text); }
return sayAlert;
}
var say2 = sayHello2("Jane");
say2(); // 輸出"Hello Jane"
調(diào)用sayHello2()函數(shù)返回了sayAlert,它是一個(gè)引用變量,指向一個(gè)函數(shù)。相信大多數(shù)JavaScript程序員能夠理解什么是引用變量,而C程序員則可以把sayAlert以及say2理解為指向函數(shù)的指針。
C指針與JavaScript引用變量并無實(shí)質(zhì)區(qū)分。在JavaScript中,不妨這樣理解,指向函數(shù)的引用變量不僅指向函數(shù)本身,還隱含地指向了一個(gè)閉包。
代碼中匿名函數(shù)function() { alert(text); }是在另一個(gè)函數(shù),即sayHello2()中定義的。在JavaScript中,如果你在函數(shù)中定義了一個(gè)函數(shù),則創(chuàng)建了閉包。
對(duì)于C語言,以及其他絕大多數(shù)語言:函數(shù)return之后,其局部變量將無法訪問,因?yàn)閮?nèi)存中的堆棧會(huì)被銷毀。
對(duì)于JavaScript,如果你在函數(shù)中定義函數(shù)的話,當(dāng)外層函數(shù)return之后,其局部變量仍然可以訪問。代碼中已經(jīng)證明了這一點(diǎn):當(dāng)sayHello2()函數(shù)return之后,我們調(diào)用了say2()函數(shù),成功打印了text變量,而text變量正是sayHello2()函數(shù)的局部變量。
如果只是從定義的角度去理解閉包,顯然是非常困難。然而,如果通過代碼示例去理解閉包,則簡單很多。因此,強(qiáng)烈建議你認(rèn)真地理解每一個(gè)示例,弄清楚它們是如何運(yùn)行的,這樣你會(huì)避免很多奇怪的BUG。
Example 3中,say667()函數(shù)return后,num變量將仍然保留在內(nèi)存中。并且,sayNumba函數(shù)中的num變量并非復(fù)制而是引用,因此它輸出的是667而非666。
function say667() {
var num = 666; // say667()函數(shù)return后,num變量將仍然保留在內(nèi)存中
var sayAlert = function() { console.log(num); }
num++;
return sayAlert;
}
var sayNumba = say667();
sayNumba(); // 輸出667
Example 4中,3個(gè)全局函數(shù)gAlertNumber,gIncreaseNumber,gSetNumber指向了同一個(gè)閉包,因?yàn)樗鼈兪窃谕淮?strong>setupSomeGlobals()調(diào)用中聲明的。它們所指向的閉包就是setupSomeGlobals()函數(shù)的局部變量,包括了num變量。也就是說,它們操作的是同一個(gè)num變量。
function setupSomeGlobals() {
var num = 666;
gAlertNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gAlertNumber(); // 輸出666
gIncreaseNumber();
gAlertNumber(); // 輸出667
gSetNumber(5);
gAlertNumber(); // 輸出5
Example 5的代碼比較難,不少人都會(huì)犯同樣的錯(cuò)誤,因?yàn)樗膱?zhí)行結(jié)果很可能違背了你的直覺。
function buildList(list)
{
var result = [];
for (var i = 0; i < list.length; i++)
{
var item = 'item' + list[i];
result.push( function() { console.log(item + ' ' + list[i])} );
}
return result;
}
var fnlist = buildList([1,2,3]);
for (var j = 0; j < fnlist.length; j++)
{
fnlist[j](); // 連續(xù)輸出3個(gè)"item3 undefined"
}
result.push( function() {alert(item + ' ' + list[i])}將指向匿名函數(shù)function() {alert(item + ' ' + list[i])}的引用變量加入了數(shù)組,其效果等價(jià)于:
pointer = function() {alert(item + ' ' + list[i])};
result.push(pointer);
代碼執(zhí)行后,連續(xù)輸出了3個(gè)"item3 undefined",明顯與直覺不同。
調(diào)用buildList()函數(shù)之后,我們得到了一個(gè)數(shù)組,數(shù)組中有3個(gè)函數(shù),而這3個(gè)函數(shù)指向了同一個(gè)閉包。而閉包中的item變量值為"item3",i變量值為3。如果理解了3個(gè)函數(shù)指向的是同一個(gè)閉包,則輸出結(jié)果就不難理解了。
Example 6中,alice變量在sayAlert函數(shù)之后定義,這并未影響代碼執(zhí)行。因?yàn)榉祷睾瘮?shù)sayAlice2所指向的閉包會(huì)包含sayAlice()函數(shù)中的所有局部變量,這自然包括了alice變量,因此可以正常打印"Hello Alice"。
function sayAlice()
{
var sayAlert = function() { console.log(alice); }
var alice = 'Hello Alice';
return sayAlert;
}
var sayAlice2 = sayAlice();
sayAlice2(); // 輸出"Hello Alice"
由Example 7可知,每次調(diào)用newClosure()都會(huì)創(chuàng)建獨(dú)立的閉包,它們的局部變量num與ref的值并不相同。
function newClosure(someNum, someRef)
{
var anArray = [1,2,3];
var num = someNum;
var ref = someRef;
return function(x)
{
num += x;
anArray.push(num);
console.log('num: ' + num + "; " + 'anArray ' + anArray.toString() + "; " + 'ref.someVar ' + ref.someVar);
}
}
closure1 = newClosure(40, {someVar: "closure 1"});
closure2 = newClosure(1000, {someVar: "closure 2"});
closure1(5); // 打印"num: 45; anArray 1,2,3,45; ref.someVar closure 1"
closure2(-10); // 打印"num: 990; anArray 1,2,3,990; ref.someVar closure 2"
嚴(yán)格來講,我對(duì)閉包的解釋并不準(zhǔn)確。不過,將閉包簡單地看做局部變量,理解起來會(huì)更加簡單。
Fundebug專注于JavaScript、微信小程序、微信小游戲、支付寶小程序、React Native、Node.js和Java實(shí)時(shí)BUG監(jiān)控。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了7億+錯(cuò)誤事件,得到了Google、360、金山軟件、百姓網(wǎng)等眾多知名用戶的認(rèn)可。歡迎免費(fèi)試用!
轉(zhuǎn)載時(shí)請(qǐng)注明作者Fundebug以及本文地址:
https://blog.fundebug.com/2017/08/07/javascript-closure-examples/