成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

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

teren / 2840人閱讀

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

前言

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

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

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

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

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

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

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

為了搞懂這個問題,我們從最簡單的循環(huán)開始

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

毫無疑問,i會被逐次打印出來

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

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

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

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

setTimeout在循環(huán)里

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

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

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

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

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

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

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

這并沒有解決問題

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

為了測試究竟是不是時間的問題,我采用了下面這種更加殘暴的方式:

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出來的結(jié)果會在一個中間點,很難是100。

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

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

-setTimeout為什么會在最后執(zhí)行呢,這是因為setTimeout的一種機制,setTimeout是從任務(wù)隊列結(jié)束的時候開始計時的,如果前面有進程沒有結(jié)束,那么它就等到它結(jié)束再開始計時。在這里,任務(wù)隊列就是它自己所在的循環(huán)。循環(huán)結(jié)束setTimeout才開始計時,所以無論如何,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)
}

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

下面這個方法則不行:

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

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

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

循環(huán)中的事件

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

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

但是和setTimeout不一樣的是,事件是需要觸發(fā)的,而絕大多數(shù)情況下,觸發(fā)的時候循環(huán)已經(jīng)結(jié)束了,所以循環(huán)相關(guān)的變量就是最后一次的取值,比如上例中,點擊body以后console 5次5,通過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,因為兩次循環(huán)使用了同樣的全局變量i,你點擊的時候這個i已經(jīng)變成了5,不管事件是在兩次循環(huán)里綁定的還是五次循環(huán)里綁定的,點擊回調(diào)只認全局變量i,跟在哪綁定的沒關(guān)系。

如果我們想要2次2和5次5,就需要把前一次循環(huán)放到函數(shù)作用域里或者把其中一個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類似,也是通過listner形參緩存循環(huán)中的變量,以下代碼中,函數(shù)a返回一個函數(shù),因為addeventlistner第二個參數(shù)接受的是函數(shù),所以要這么寫,而要執(zhí)行的內(nèi)容,寫在返回的這個函數(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é)

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

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

如果想在setTimeout和綁定事件保存住循環(huán)過程中產(chǎn)生的變量,需要通過函數(shù)的實參傳進函數(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

測試文檔

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

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

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/92316.html

相關(guān)文章

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

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

    nidaye 評論0 收藏0
  • 新鮮出爐的8月前端面試題

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

    qingshanli1988 評論0 收藏0
  • JavaScript中的閉包

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

    Donne 評論0 收藏0
  • JavaScript閉包 的詳解

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

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

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

    lewif 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<