成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[Javascript實(shí)驗(yàn)課]循環(huán)中的閉包

teren / 2815人閱讀

摘要:執(zhí)行出來(lái)的結(jié)果是這樣的實(shí)驗(yàn)發(fā)現(xiàn),無(wú)論如何都在最后執(zhí)行,這證實(shí)了我們之前遇到的問(wèn)題,因?yàn)樵谘h(huán)結(jié)束才執(zhí)行,所以回調(diào)函數(shù)調(diào)用的取值必然是循環(huán)的最后一次。

前言

https://developer.mozilla.org/zh-CN/docs/JavaScript/Guide/Closures
MDN上描述閉包的章節(jié)闡述了一個(gè)由于閉包產(chǎn)生的常見(jiàn)錯(cuò)誤,代碼片段是這樣的

for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }

簡(jiǎn)言之就是循環(huán)中為不同的元素綁定事件,事件回調(diào)函數(shù)里如果調(diào)用了跟循環(huán)相關(guān)的變量,則這個(gè)變量取循環(huán)的最后一個(gè)值。

由于綁定的回調(diào)函數(shù)是一個(gè)匿名函數(shù),所以文中把造成這個(gè)現(xiàn)象的原因歸結(jié)為 這個(gè)函數(shù)是一個(gè)閉包,攜帶的作用域?yàn)橥鈱幼饔糜?,?dāng)事件觸發(fā)的時(shí)候,作用域中的變量已經(jīng)隨著循環(huán)走到最后了。

注:閉包 = 函數(shù) + 創(chuàng)建該函數(shù)的環(huán)境

我對(duì)此產(chǎn)生了很多疑問(wèn),如果說(shuō)閉包是函數(shù)和創(chuàng)建時(shí)的環(huán)境,那么事件綁定的時(shí)候(也就是這個(gè)匿名函數(shù)創(chuàng)建的時(shí)候),循環(huán)中的環(huán)境應(yīng)該是循環(huán)當(dāng)次,為什么直接到最后一次了呢?下面我們就一步一步分析,究竟是什么原因造成的。

簡(jiǎn)單循環(huán)中的i

為了搞懂這個(gè)問(wèn)題,我們從最簡(jiǎn)單的循環(huán)開(kāi)始

for (var i = 0; i < 5; i++) {
     console.log(i)
}

毫無(wú)疑問(wèn),i會(huì)被逐次打印出來(lái)

for (var i = 0; i < 5; i++) {
    var a = function(){
        console.log(i)
    }
    a()
}

這里,i也會(huì)被逐次打印出來(lái),因?yàn)閖s里,外層函數(shù)作用域會(huì)影響內(nèi)層,而內(nèi)層不會(huì)影響外層?;谶@個(gè)原理,我們也可以加多少層都沒(méi)關(guān)系:

for (var i = 0; i < 5; i++) {
    var a = function(){
        return function(){
            console.log(i)
        }
    }
    a()()
}

每一層匿名函數(shù)和變量i都組成了一個(gè)閉包,但是這樣在循環(huán)中并沒(méi)有問(wèn)題,因?yàn)楹瘮?shù)在循環(huán)體中立即被執(zhí)行了。setTimeout和事件則不太一樣,詳見(jiàn)下文。

setTimeout在循環(huán)里

-setTimeout在循環(huán)中會(huì)怎樣呢?

for (var i = 0; i < 5; i++) {
    setTimeout(function(){
        console.log(i)
    },10)
}

不出所料,這里果然出問(wèn)題了,打印出來(lái)的結(jié)果為5個(gè)5,遇到了前言中所述的由于閉包所引起的常見(jiàn)錯(cuò)誤。

根據(jù)內(nèi)部可調(diào)用外部作用域的原理,setTimeout的回調(diào)函數(shù)里面調(diào)用了外層的i,i和回調(diào)函數(shù)組成了閉包。i在循環(huán)執(zhí)行之前是0,循環(huán)之后是5。

一切都順理成章,很好理解,問(wèn)題就是為什么setTimeout的回調(diào)不是每次取循環(huán)時(shí)的值,而取最后一次的值,難道setTimeout回調(diào)是在循環(huán)體外觸發(fā)的?

會(huì)不會(huì)是時(shí)間的問(wèn)題,我們把setTimeout的回調(diào)延遲設(shè)為0毫秒試一下。

for (var i = 0; i < 5; i++) { 
    var a = function(){
        console.log(i)
    }
    setTimeout(a,0)
}

這并沒(méi)有解決問(wèn)題

另注:其實(shí)setTimeout的延遲時(shí)間是存在最小值的,根據(jù)瀏覽器的不同有可能是4ms 或者5ms,這意味著就算setTimeout設(shè)為0,還是有一小段的延遲的。
詳見(jiàn):https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout#Notes

為了測(cè)試究竟是不是時(shí)間的問(wèn)題,我采用了下面這種更加殘暴的方式:

for (var i = 0; i < 100; i++) { 
    var a = function(){
        console.log(i)
    }
    a();
    setTimeout(a,0)
}

循環(huán)100次,一次普通調(diào)用,一次在setTimeout里面調(diào)用,如果存在延遲,那么setTimeout出來(lái)的結(jié)果會(huì)在一個(gè)中間點(diǎn),很難是100。

執(zhí)行出來(lái)的結(jié)果是這樣的:

實(shí)驗(yàn)發(fā)現(xiàn),無(wú)論如何setTimeout都在最后執(zhí)行,這證實(shí)了我們之前遇到的問(wèn)題,因?yàn)?b>setTimeout在循環(huán)結(jié)束才執(zhí)行,所以回調(diào)函數(shù)調(diào)用的i取值必然是循環(huán)的最后一次。

-setTimeout為什么會(huì)在最后執(zhí)行呢,這是因?yàn)?b>setTimeout的一種機(jī)制,setTimeout是從任務(wù)隊(duì)列結(jié)束的時(shí)候開(kāi)始計(jì)時(shí)的,如果前面有進(jìn)程沒(méi)有結(jié)束,那么它就等到它結(jié)束再開(kāi)始計(jì)時(shí)。在這里,任務(wù)隊(duì)列就是它自己所在的循環(huán)。循環(huán)結(jié)束setTimeout才開(kāi)始計(jì)時(shí),所以無(wú)論如何,setTimeout里面的i都是最后一次循環(huán)的i。

解決辦法如下:

for (var i = 0; i < 5; i++) {
    var a = function(v){
        return function(){
            console.log(v)
        }
    }
    setTimeout(a(i),0)
}

很多人能利用上面的方法解決這個(gè)問(wèn)題,因?yàn)?b>setTimeout第一個(gè)參數(shù)需要一個(gè)函數(shù),所以返回一個(gè)函數(shù)給它,返回的同時(shí)把i作為參數(shù)傳進(jìn)去,通過(guò)形參v緩存了i,并帶進(jìn)返回的函數(shù)里面。

下面這個(gè)方法則不行:

for (var i = 0; i < 5; i++) {
    var a = function(v){
        return function(){
            console.log(v)
        }
    }
    setTimeout(function(){
         a(i)
    },0)
}

這里的問(wèn)題是,回調(diào)函數(shù)沒(méi)有立即執(zhí)行,本身又沒(méi)有傳入?yún)?shù)緩存。

總結(jié):例子中遇到setTimeout的問(wèn)題,罪魁禍?zhǔn)资腔卣{(diào)等待循環(huán)隊(duì)列結(jié)束造成的,解決的辦法是給回調(diào)函數(shù)傳一個(gè)實(shí)參緩存循環(huán)的數(shù)據(jù)。

循環(huán)中的事件

循環(huán)中的事件和setTimeout類似,也會(huì)涉及閉包問(wèn)題,事件的listener,會(huì)和循環(huán)相關(guān)的變量形成一個(gè)閉包,在執(zhí)行l(wèi)istener的時(shí)候,變量取最后一次循環(huán)的值。

for (var i = 0; i < 5; i++) { 
    var a = function(){
        console.log(i) 
    }
    document.body.addEventListener("click",a) 
}

但是和setTimeout不一樣的是,事件是需要觸發(fā)的,而絕大多數(shù)情況下,觸發(fā)的時(shí)候循環(huán)已經(jīng)結(jié)束了,所以循環(huán)相關(guān)的變量就是最后一次的取值,比如上例中,點(diǎn)擊body以后console 5次5,通過(guò)addEventListener添加的事件是可以疊加的。

考慮下面的代碼:

for (var i = 0; i < 2; i++) { 
    var a = function(){
        console.log(i) 
    }
    document.body.addEventListener("click",a) 
}

for (var i = 0; i < 5; i++) { 
    var a = function(){
        console.log(i) 
    }
    document.body.addEventListener("click",a) 
}

答案是:

2次5和5次5,因?yàn)閮纱窝h(huán)使用了同樣的全局變量i,你點(diǎn)擊的時(shí)候這個(gè)i已經(jīng)變成了5,不管事件是在兩次循環(huán)里綁定的還是五次循環(huán)里綁定的,點(diǎn)擊回調(diào)只認(rèn)全局變量i,跟在哪綁定的沒(méi)關(guān)系。

如果我們想要2次2和5次5,就需要把前一次循環(huán)放到函數(shù)作用域里或者把其中一個(gè)i換成別的變量名

(function(){
    for (var i = 0; i < 2; i++) { 
        var a = function(){
            console.log(i) 
        }
        document.body.addEventListener("click",a) 
    }

})()
for (var i = 0; i < 5; i++) { 
    var a = function(){
        console.log(i) 
    }
    document.body.addEventListener("click",a) 
}

至于解法,和setTimeout類似,也是通過(guò)listner形參緩存循環(huán)中的變量,以下代碼中,函數(shù)a返回一個(gè)函數(shù),因?yàn)?b>addeventlistner第二個(gè)參數(shù)接受的是函數(shù),所以要這么寫(xiě),而要執(zhí)行的內(nèi)容,寫(xiě)在返回的這個(gè)函數(shù)體內(nèi)。

for (var i = 0; i < 5; i++) { 
    var a = function(v){
        return function(){
            console.log(v)
        }
    }
    document.body.addEventListener("click",a(i))
}
總結(jié)

閉包并沒(méi)有那么復(fù)雜,可以簡(jiǎn)單的理解為函數(shù)體和外部作用域的一種關(guān)聯(lián)。

-setTimeout和綁定事件在循環(huán)經(jīng)常會(huì)帶來(lái)意想不到的效果,取決于這兩個(gè)函數(shù)的特殊機(jī)制,閉包不是主因。

如果想在setTimeout和綁定事件保存住循環(huán)過(guò)程中產(chǎn)生的變量,需要通過(guò)函數(shù)的實(shí)參傳進(jìn)函數(shù)體。

參考(感謝以下作者):

http://www.cnblogs.com/hongdada/p/3359668.html

http://www.cnblogs.com/hh54188/p/3153358.html

https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener

https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout

http://zh.wikipedia.org/wiki/%E9%97%AD%E5%8C%85_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)

https://developer.mozilla.org/zh-CN/docs/JavaScript/Guide/Closures

測(cè)試文檔

http://jsfiddle.net/fishenal/wfU56/3/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92316.html

相關(guān)文章

  • chrome下的Javascript的任務(wù)機(jī)制

    摘要:在第一次循環(huán)的時(shí)候并沒(méi)有被賦值,所以是,在第二次循環(huán)的時(shí)候,定時(shí)器其實(shí)清理的是上一個(gè)循環(huán)的定時(shí)器。所以導(dǎo)致每次循環(huán)都是清理上一次的定時(shí)器,而最后一次循環(huán)的定時(shí)器沒(méi)被清理,導(dǎo)致一直輸出。 Javascript Evet Loop 模型 setTimeout()最短的事件間隔是4mssetInterval()最短的事件間隔是10ms以上這個(gè)理論反正我是沒(méi)有驗(yàn)證過(guò) Exemple 1 --...

    nidaye 評(píng)論0 收藏0
  • 新鮮出爐的8月前端面試題

    摘要:前言最近參加了幾場(chǎng)面試,積累了一些高頻面試題,我把面試題分為兩類,一種是基礎(chǔ)試題主要考察前端技基礎(chǔ)是否扎實(shí),是否能夠?qū)⑶岸酥R(shí)體系串聯(lián)。 前言 最近參加了幾場(chǎng)面試,積累了一些高頻面試題,我把面試題分為兩類,一種是基礎(chǔ)試題: 主要考察前端技基礎(chǔ)是否扎實(shí),是否能夠?qū)⑶岸酥R(shí)體系串聯(lián)。一種是開(kāi)放式問(wèn)題: 考察業(yè)務(wù)積累,是否有自己的思考,思考問(wèn)題的方式,這類問(wèn)題沒(méi)有標(biāo)準(zhǔn)答案。 基礎(chǔ)題 題目的答...

    qingshanli1988 評(píng)論0 收藏0
  • JavaScript中的閉包

    摘要:權(quán)威指南第版中閉包的定義函數(shù)對(duì)象可以通過(guò)作用域鏈相互關(guān)聯(lián)起來(lái),函數(shù)體內(nèi)部的變量都可以保存在函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中成為閉包。循環(huán)中的閉包使用閉包時(shí)一種常見(jiàn)的錯(cuò)誤情況是循環(huán)中的閉包,很多初學(xué)者都遇到了這個(gè)問(wèn)題。 閉包簡(jiǎn)介 閉包是JavaScript的重要特性,那么什么是閉包? 《JavaScript高級(jí)程序設(shè)計(jì)(第3版)》中閉包的定義: 閉包就是指有權(quán)訪問(wèn)另一個(gè)函數(shù)中的變...

    Donne 評(píng)論0 收藏0
  • JavaScript閉包 的詳解

    摘要:局部變量,當(dāng)定義該變量的函數(shù)調(diào)用結(jié)束時(shí),該變量就會(huì)被垃圾回收機(jī)制回收而銷毀。如果在函數(shù)中不使用匿名函數(shù)創(chuàng)建閉包,而是通過(guò)引用一個(gè)外部函數(shù),也不會(huì)出現(xiàn)循環(huán)引用的問(wèn)題。 閉包是什么 在 JavaScript 中,閉包是一個(gè)讓人很難弄懂的概念。ECMAScript 中給閉包的定義是:閉包,指的是詞法表示包括不被計(jì)算的變量的函數(shù),也就是說(shuō),函數(shù)可以使用函數(shù)之外定義的變量。 是不是看完這個(gè)定義感...

    longshengwang 評(píng)論0 收藏0
  • JavaScript系列——JavaScript同步、異步、回調(diào)執(zhí)行順序之經(jīng)典閉包setTimeou

    摘要:同步異步回調(diào)傻傻分不清楚。分割線上面主要講了同步和回調(diào)執(zhí)行順序的問(wèn)題,接著我就舉一個(gè)包含同步異步回調(diào)的例子。同步優(yōu)先回調(diào)內(nèi)部有個(gè),第二個(gè)是一個(gè)回調(diào)回調(diào)墊底。異步也,輪到回調(diào)的孩子們回調(diào),出來(lái)執(zhí)行了。 同步、異步、回調(diào)?傻傻分不清楚。 大家注意了,教大家一道口訣: 同步優(yōu)先、異步靠邊、回調(diào)墊底(讀起來(lái)不順) 用公式表達(dá)就是: 同步 => 異步 => 回調(diào) 這口訣有什么用呢?用來(lái)對(duì)付面試的...

    lewif 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<