坑爹的JS閉包,怎麼去理解纔是正確的

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

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

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

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

1

2

3

4

5

6

7

8

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的內存機制有木有。

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

1

2

3

4

5

6

7

8

function foo(x) {

    var tmp = 3;

    function bar(y) {

        alert(x + y + (++tmp));

    }

    bar(10);

}

foo(2)

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

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

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

1

2

3

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中沒有塊級作用域的說法,所以得利用函數自己創建一個作用域:

1

2

3

4

5

6

7

8

9

var btnList = document.getElementsByClassName("btn"),

      len = btnList.length;

forvar 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 並沒有被回收,依然在等待你使用。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章