坑爹的JS閉包,怎么去理解才是正確的


  有好些天沒寫文章了,前面一直在搭建自己的博客后台,由於域名備案一直沒有下來,就只能繼續在這里寫了。

  今天寫些什么了,一般大家比較關心,尤其學JS的,就是又愛又恨的一個概念——閉包。

  說老實話,這個概念大家百度可能會百度到一堆的答案,但真正能弄懂的又有幾個了?在這里我不是打廣告,到目前為止,對閉包描述最清晰正確的在《你不知道的javascript》一書中。而且這本書很適合各位對javascript這門編程語言深入了解。

  說了這么多,那么閉包的定義到底是什么了。大家一定要注意,不是說能夠訪問到其他作用域的變量就是閉包,這是很籠統的。准確來說,閉包是基於正常的垃圾回收處理機制下的。也就是說,一般情況一個函數(函數作用域)執行完畢,里面聲明的變量會全部釋放,被垃圾回收器回收。但閉包利用一個技巧,讓作用域里面的變量,在函數執行完之后依舊保存沒有被垃圾回收處理掉。

  可以文字你不太喜歡看,那好,我直接上代碼。這可能是很多解說閉包的案列,先拿來用再說。

function foo(x) {
    var tmp = 3;
    return function (y) {
        alert(x + y + (++tmp));
    }
}
var bar = foo(2); // bar 現在是一個閉包
bar(10);

  我們來分析一下這一段代碼,在foo中,聲明一個變量tmp,他屬於foo作用域下的變量。函數返回一個函數,這個函數被嵌套,函數內部彈出x+y(++tmp)。這是個人都看得懂啊,那為什么會出現閉包,怎么出現的了。這接下來就是看執行的過程了,首先執行var bar = foo(2);那么foo就執行了,參數2也傳進去了,但是執行完畢之后,tmp變量以及參數x就已經被釋放回收了嗎?並沒有,因為返回值里面還等待使用這些變量咯,所以此時,foo雖然執行了,但是foo的變量並沒有被釋放,在return在等待繼續使用這些變量了,這個時候bar就是一個閉包。

  然后我們再執行bar,結果是16,另外你再把bar里面的參數改改,是不是結果又變化了勒?這就是閉包的神奇之處,它改變了JS的內存機制有木有。

  然后我們再看看長得很像閉包的形式

function foo(x) {
    var tmp = 3;
    function bar(y) {
        alert(x + y + (++tmp));
    }
    bar(10);
}
foo(2)

  如果按照某些教程說的,可以父級作用域訪問子級作用域的變量,foo在全局中執行,執行過程中未必沒有訪問局部變量?訪問到了吧,但他不是閉包。按照我之前說的思維再走一遍。

  函數foo執行,執行完執行完畢之后沒我再執行的時候,是不是里面的tmp,bar函數又重新聲明了。那么根本就沒有阻止foo作用域中的變量被垃圾回收吧,那怎么又叫做閉包了?

  再結合一個閉包運用的最多的例子,就是for循環的問題,比如:

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

  這看起來沒有任何問題啊,會輸出0,1,2,3,4,5,6,7,8,9

  一般出問題出在哪里了,比如為很多個元素綁定一個點擊事件的時候

  我們期望的是點擊到某一個按鈕就會輸出第幾個被點擊到了語句,但是很遺憾,你永遠達不到你想要的效果,為什么了。

  因為你點擊事件是在點擊后才觸發的,而for循環當你執行只后就已經全部執行完畢了執行完畢后 i 的值會是比len大 1 的,所以不管你怎么點, i 的值在for執行完畢之后已經固定了,改變不了了。那有什么辦法保存這個 i 的值了。除了最基礎的給元素節點自定義屬性這個方法之外,就是我們所說的閉包了。那么,怎么用?

  我現在的需求是要保存住這個 i 的值,如果在當前作用域下是做不到的。而閉包的作用就是讓當前作用域的值不會被垃圾回收,由於在ES5中沒有塊級作用域的說法,所以得利用函數自己創建一個作用域:

var btnList = document.getElementsByClassName("btn"),
      len = btnList.length;
for(var i = 0;i<len;i++){
     (function(j){
            btnList[j].onclick = function(){
            console.log("第"+j+"個按鈕被點擊到了") 
         }    
    })(i)
}    

  這又哪里產生了閉包了。別急,我們一個個分析。for循環每一次都執行一個 IIEF (自執行函數),每一次變量 i 被當做參數傳到IIEF中去 , 那么這個自執行函數中創建了一個變量,參數 j 然后元素節點 btnList 綁定一個onclick事件,執行函數里面需要用到這個參數 j ,但是你又沒點 , 那么這個遍歷 j 就沒有被清理 , 就一直在參數里面被保存着 , 每一個IIEF都做一樣的事情 , 所以這個時候就產生了閉包 , 變量 j 並沒有被回收,依然在等待你使用。

  不知道你是否真正弄懂了哈,在看閉包之前先得把作用域給理解,否則你也是看不懂的。

  實際上,在很多同學在平時練習過程中或多或少都用到過閉包,只不過因為概念不清晰所以你才不知道,現在翻翻你之前寫的代碼,如果真正懂了,那么你會很輕易就找到你使用過的閉包了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM