關(guān)于js中for循環(huán)里閉包的一些思考

在文章的開(kāi)頭陈肛,簡(jiǎn)單來(lái)提一下揍鸟,什么是閉包。

JavaScript中的閉包,就像一個(gè)副本阳藻,將某函數(shù)在退出時(shí)候的所有局部變量復(fù)制保存其中晰奖。 這些函數(shù)可以“記憶”他被創(chuàng)建時(shí)候的上下文環(huán)境,將外部變量保留在棧幀中腥泥。

首先匾南,我們來(lái)看這樣一個(gè)例子:

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}
function testList() {
    var fnlist = buildList([1,2,3]);
    // 使用j是為了防止搞混---可以使用i
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}
 testList() //輸出 "item2 undefined" 3 次

在上面這個(gè)例子中,console會(huì)輸出三次“item2 undefined”

01.png

為什么沒(méi)有按照f(shuō)or循環(huán)的順序蛔外,輸出 item0蛆楞,item1item2的值呢夹厌?
這是因?yàn)樵谏鲜龃a中豹爹,for循環(huán)里產(chǎn)生了一個(gè)閉包,當(dāng)result.push( function() {console.log(item + ' ' + list[i])} );這行代碼運(yùn)行時(shí)矛纹,由于i變量并沒(méi)有在這個(gè)無(wú)名函數(shù)中定義臂聋,所以會(huì)到上層語(yǔ)義環(huán)境中去找。此時(shí)或南,for循環(huán)并不會(huì)因此中斷孩等,等待無(wú)名函數(shù)。那么當(dāng)無(wú)名函數(shù)在上層語(yǔ)義環(huán)境中找到i的值采够,這是for循環(huán)已經(jīng)結(jié)束肄方,i的值自然變成了3,而item此時(shí)的值則為item2.
那么在接下來(lái)的for循環(huán)中

function testList() {
    var fnlist = buildList([1,2,3]);
    // 使用j是為了防止搞混---可以使用i
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

實(shí)際上時(shí)運(yùn)行了三次console.log('item2' + ' ' + list[3])蹬癌,由于傳入函數(shù)bulidList的數(shù)組為[1, 2, 3]权她,list[3]的值類型自然是undefined
若是我們將傳入的數(shù)組做出一點(diǎn)修改冀瓦,就會(huì)發(fā)現(xiàn)伴奥,console會(huì)將list[3]的值正確輸出。第一個(gè)for循環(huán)中的判斷條件由i < list.length改為i < 3翼闽,傳入的數(shù)組由[1, 2, 3]改為[1, 2, 3, 4]拾徙。

function buildList(list) {
    var result = [];
    for (var i = 0; i < 3; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}
function testList() {
    var fnlist = buildList([1, 2, 3, 4]);
    // 使用j是為了防止搞混---可以使用i
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}
 testList() //輸出 "item2 4" 3 次
02.png

如果在for循環(huán)中產(chǎn)生了閉包,我們?nèi)绾巫屗敵鑫覀兿胍慕Y(jié)果呢感局?再來(lái)看一個(gè)例子吧尼啡。

var list = document.getElementById("list");

 //插入五個(gè)<li>標(biāo)簽
for ( i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i)); 

//分別為五個(gè)<li>標(biāo)簽綁定onclick事件
  item.onclick = function (ev) {
    console.log("Item " + i + " is clicked.");
  };
  list.appendChild(item);
}

這個(gè)例子中,我們想要的效果是點(diǎn)擊不同的<li>標(biāo)簽询微,console會(huì)輸出對(duì)應(yīng)的Itemi is cilick崖瞭。但是由于for循環(huán)里面產(chǎn)生了閉包,實(shí)際的結(jié)果是無(wú)論點(diǎn)擊哪個(gè)<li>撑毛,console輸出的都是Item 6 is clicked.

03.png

如果我們將代碼稍作修改书聚,再增加一層閉包,并將i作為參數(shù)傳入到函數(shù)中,我們將會(huì)得到正確地輸出雌续。

for ( i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  (function(i){
  item.onclick = function (ev) {
    console.log("Item " + i + " is clicked.");
  };
  list.appendChild(item);
  })(i);
}

04.png

這段代碼中斩个,雖然console.log("Item " + i + " is clicked.");仍然需要去上層語(yǔ)義環(huán)境中找i的值,但是由于外面增加了一個(gè)function驯杜,并將i作為參數(shù)傳入受啥,此時(shí)便可以尋找到正確地值。在這里鸽心,每一次for循環(huán)滚局,都會(huì)產(chǎn)生一個(gè)大的閉包,實(shí)際上到循環(huán)結(jié)束顽频,共產(chǎn)生了五個(gè)閉包藤肢,這五個(gè)閉包里面分別存儲(chǔ)了i從1-5的五個(gè)值。如果我們不將i作為參數(shù)傳入會(huì)是什么樣的糯景?

for ( i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  (function(){
  item.onclick = function (ev) {
    console.log("Item " + i + " is clicked.");
  };
  list.appendChild(item);
  })();
}

05.png

可以看到谤草,跟之前沒(méi)有在外層套上函數(shù)時(shí)是一樣的輸出。那么有沒(méi)有什么辦法不傳i作為參數(shù)也可以得到正確的輸出呢莺奸?有的!看下面的代碼冀宴。

for ( i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  (function(){
    var j = i;
    item.onclick = function (ev) {
        console.log("Item " + j + " is clicked.");
    };
    list.appendChild(item);
    })();
}

這段代碼中灭贷,我增加了var j = i;,并將之前的i改為j略贮。此時(shí)甚疟,已經(jīng)能夠得到正確的輸出。

06.png

為什么增加了一個(gè)var j = i就可以得到正確的輸出了呢逃延?實(shí)際上原理和上面并沒(méi)有變化览妖,主要是因?yàn)檫@里產(chǎn)生了五個(gè)閉包,每一個(gè)閉包里面的j都引用了一個(gè)i值揽祥。但再稍加修改讽膏,就又會(huì)不同。接下來(lái)拄丰,我將var j = i;改為j = i;府树,看看會(huì)有什么變化。

07.png

這里的輸出又出錯(cuò)了料按,會(huì)得到五個(gè)同樣的輸出奄侠。但是請(qǐng)注意了雖然同是同樣的輸出,卻與之前略有不同载矿。這里的五個(gè)輸出都是Item 5 is clicked.而之前則是Item 6 is clicked.
得到錯(cuò)誤的輸出是因?yàn)槿サ袅岁P(guān)鍵字var之后垄潮,j的作用域發(fā)生了變化,成為了全局變量。五個(gè)j引用了同一個(gè)i值弯洗。至于為什么是5而不是6旅急,則是因?yàn)?code>j引用的是最后一次循環(huán)時(shí)的i值,而不是循環(huán)結(jié)束以后的i值涂召。
最后坠非,這篇文章中的內(nèi)容,是我在看ES6標(biāo)準(zhǔn)中let關(guān)鍵字相關(guān)的內(nèi)容時(shí)想到的果正。那么你肯定會(huì)問(wèn)了炎码,是不是 let也可以解決for循環(huán)中閉包的問(wèn)題?Bingo秋泳!再來(lái)看看下面的代碼吧潦闲。

for ( i = 1; i <= 5; i++) {
  var item = document.createElement("LI");
  item.appendChild(document.createTextNode("Item " + i));
  let j = i;
  item.onclick = function (ev) {
        console.log("Item " + j + " is clicked.");
    };
  list.appendChild(item);
}

這里let創(chuàng)建的變量j是擁有塊級(jí)作用域的,在ES6之前js是沒(méi)有塊級(jí)作用域的迫皱。
當(dāng)然歉闰,解決辦法還有很多,你覺(jué)得哪種辦法最優(yōu)雅呢卓起?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末和敬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子戏阅,更是在濱河造成了極大的恐慌昼弟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕筐,死亡現(xiàn)場(chǎng)離奇詭異舱痘,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)离赫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門芭逝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人渊胸,你說(shuō)我怎么就攤上這事旬盯。” “怎么了蹬刷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵瓢捉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我办成,道長(zhǎng)泡态,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任迂卢,我火速辦了婚禮某弦,結(jié)果婚禮上桐汤,老公的妹妹穿的比我還像新娘。我一直安慰自己靶壮,他們只是感情好怔毛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著腾降,像睡著了一般拣度。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上螃壤,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天抗果,我揣著相機(jī)與錄音,去河邊找鬼奸晴。 笑死冤馏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寄啼。 我是一名探鬼主播逮光,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼墩划!你這毒婦竟也來(lái)了涕刚?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤乙帮,失蹤者是張志新(化名)和其女友劉穎副女,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蚣旱,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年戴陡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了塞绿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恤批,死狀恐怖异吻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情喜庞,我是刑警寧澤诀浪,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站延都,受9級(jí)特大地震影響雷猪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晰房,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一求摇、第九天 我趴在偏房一處隱蔽的房頂上張望射沟。 院中可真熱鬧,春花似錦与境、人聲如沸验夯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挥转。三九已至,卻和暖如春共屈,著一層夾襖步出監(jiān)牢的瞬間绑谣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工趁俊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留域仇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓寺擂,卻偏偏與公主長(zhǎng)得像暇务,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怔软,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包垦细,大牛和功能型程序員請(qǐng)自行忽略。 譯者...
    KX九五閱讀 278評(píng)論 0 1
  • 前言 總括 :這篇文章使用有效的javascript代碼向程序員們解釋了閉包挡逼,大牛和功能型程序員請(qǐng)自行忽略括改。 譯者...
    秦至閱讀 744評(píng)論 0 19
  • 前言 前幾天看了一篇關(guān)于閉包的文章,才想起還有閉包這東西家坎,好久沒(méi)有用到嘱能,都快把它忘記了。于是才復(fù)習(xí)一下虱疏,記下這文章...
    yimi珊閱讀 517評(píng)論 0 1
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)惹骂。 張土汪:刷leetcod...
    土汪閱讀 12,744評(píng)論 0 33
  • 屋中身覺(jué)瑟瑟抖,出門始知朗朗天做瞪。 冬日融融嫌衣多对粪,暖風(fēng)徐徐心自閑。
    跡遠(yuǎn)留香閱讀 146評(píng)論 0 0