閉包一直是許多初學者的難題,網上對閉包的講解也是衆說紛紜,但還是許多人不能明白。
下面我通過五個簡單例子,讓你明白閉包原理。
第一個例子
<script>
var i = 0;
document.onclick = addNumber;
function addNumber(){
i++;
document.title =i;
};
</script>
第一個例子都能看懂,是個簡簡單單的實現 i 累加。無需多說,我們繼續看第二個例子。
第二個例子
<script>
document.onclick = addNumber;
function addNumber(){
var i = 0;
i++;
document.title =i;
};
</script>
第二個例子:我們會發現 i 這時候無法實現累加,一直顯示是1。這是爲什麼?
原因是:
1、第一個例子的 i 定義在函數外面,它在一個叫做"全局作用域"的區域裏面。"全局作用域"只會在瀏覽器窗口關閉或頁面刷新的時候進行關閉。(我們跟第二個例子對比下就知道全局作用域)。
2、第二個例子的 i 定義在函數裏面,它在一個叫做"局部作用域"的區域裏面。(局部作用域是我自己命名的,方便理解)。"局部作用域"會在函數被調用的時候創建,而在函數運行結束的時候關閉(並且裏面創建的變量跟函數也會被刪掉–垃圾回收機制)。
3、通過上面的說法:我們就知道了,第一個例子能實現累加,是因爲 i 一直儲存在全局作用域,沒有被刪掉。第二個例子,我們調用了函數,然後創建了局部作用域->定義了 i ->函數運行結束後->局部作用域關閉了,i 也被刪除了。然後我們再點擊, 又重新創建了局部作用域,重新定義 i 。(我們點擊是在不停重新創建 i)
問題:有沒有辦法讓“局部作用域”不關掉呢?那就是"閉包"了。
第三個例子:簡單的閉包
<script>
document.onclick =addNumber1();
function addNumber1(){
var i = 0;
function addNumber2() {
i++;
document.title =i;
}
return addNumber2; //運行addNumber1()的獲得的值是addNumber2
}
</script>
第三個例子: 這次還是將 i 定義到函數裏面,但我們會發現,i 實現了累加。 這說明我們已經讓局部作用域不關閉了,這就是簡單的閉包。
我們來分析爲什麼局部作用域沒有被關閉:
①我們在函數內部定義了 i
②定義了函數 addNumber2(),並且在addNumber2()引用了 i
③最後return addNumber2。
④(要注意的是:這裏document.οnclick=addNumber1(); 並不是調用addNumber1函數[調用是不加括號],而是讓addNumber1()執行了,獲取到了addNumber1() 的值)。 其實相當於 documen.onclick = addNumber2 //不能直接這麼寫,爲了方便理解,我們假設它存在 ;
⑤ 這時就會神奇的發現,我們竟然可以在全局環境中使用在函數裏面定義的函數。(一般情況下,全局環境無法使用函數裏面定義的變量,就是你在函數外面無法使用函數裏面定義的變量,[函數裏面定義的變量同理])。
⑥ 因爲documen.onclick = addNumber2;的存在(作用域鏈的存在,我會在另一篇文章中分析作用域鏈),addNumber2的執行依賴addNumber1()裏面定義的 i,使得addNumber1()不會被關閉,i 也不會被重新定義,累加的值也被成功的保存下來。
第四個例子:簡寫閉包
<script>
document.onclick =(function(){ //匿名函數自執行
var i = 0;
return function() { //返回另一個匿名函數
i++;
document.title =i;
}
})();
</script>
第四個例子:這裏涉及到 “匿名函數自執行” 。(function(){})(); 一般執行函數是在函數名後面加括號,這裏(function(){})相當於一個表達式,我們在它後面加(); 。就相當於執行了這個函數。 同樣的,我們 return function(){}; 。 也是把這個函數返回出去了,只不過返回出去一個匿名函數。
第五個例子:閉包的作用。
<script>
var i = 10 ;
document.onclick =(function(){
var i = 0;
return function() {
i++;
document.title =i;
}
})();
console.log(i);
</script>
第五個例子:有蠻多初學者說,都聽別人閉包好用,可是不知道他的實際應用。像第五個例子,我在全局也定義了一個 i ,也對它進行了控制檯輸出,發現我們閉包裏的函數並不會對全局造成影響,卻實現了全局的效果。對於比較大型的合作項目,很多人分工合作,如果每個人都在全局定義變量 ,如果定義了同樣名字的變量,對這兩個人的程序都會造成一些影響。 這就是閉包。
閉包的用處還很多,明白了原理,你可以繼續挖掘它。
還有點需要注意的是:閉包的存在會使得他不會被垃圾回收機制回收,他會比其他函數佔更多的內存,過渡使用閉包可能會導致內存佔用過多。可能會導致瀏覽器崩潰的問題。解決方法就是已經達到我們的目的的時候,解除對匿名函數的引用,沒有了引用變成了普通函數,被垃圾回收機制回收。