深入理解JS閉包

cauchy6317單身發表於2018-07-23

關於JS中閉包的理解,相信很多人都和筆者一樣剛開始很是困惑。筆者也是在看了很多前輩的文章後,總結出一點自己的理解。記錄與此,囿於筆者水平有限 ,若有錯誤之處,懇請不嗇賜教。

你可以在一個函式裡面巢狀另外一個函式。巢狀(內部)函式對其容器(外部)函式是私有的。它自身也形成了一個閉包。一個閉包是一個可以自己擁有獨立的環境與變數的的表示式(通常是函式,因為ES6有了塊級作用域的概念)。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Functions(上面這句話摘自這個網址)

第一部分:初遇閉包

http://www.runoob.com/js/js-function-closures.html

什麼是閉包?閉包有什麼作用?這是我遇到閉包時的第一反應。

閉包在JavaScript高階程式設計(第3版)中是這樣描述:閉包是指有權訪問另一個函式作用域中的變數的函式。

那麼閉包的作用也就很明顯了。

1. 可以在函式的外部訪問到函式內部的區域性變數。 
2. 讓這些變數始終儲存在記憶體中,不會隨著函式的結束而自動銷燬。

在上面的程式碼中,閉包指的就是function () {return counter += 1;}這個函式。首先解釋一下這段程式碼,在變數add被賦值之前,第一個function執行了一次(執行且僅會執行一次),因為這是一個函式表示式宣告方式並且宣告後加上了(),所以會自動執行一次。執行後add被賦值(匿名函式)了,add= function () {return counter += 1;} 。然後每次呼叫add()函式時,返回的都是這個函式,因為這個函式在第一個函式的內部,所以即使第一個函式執行完了,第二個函式依然能訪問counter(JS設計的作用域鏈,當前作用域能訪問上級的作用域)。

閉包是可以在另一個函式的外部訪問到其作用域中的變數的函式。而被訪問的變數可以和函式一同存在。即使另一個函式已經執行結束,導致建立變數的環境銷燬,也依然會存在,直到訪問變數的那個函式被銷燬。當然,如果僅僅是做一個簡單的計數器,大可不用這樣麻煩。下面這簡短的程式碼就能輕鬆實現。

var a = 0;
function myFunction(){
	a++;
    document.getElementById("demo").innerHTML = a;
}

推薦一篇部落格:https://blog.csdn.net/qq_36276528/article/details/70049825(寫得很有深度)

第二部分:牛客翻船

https://www.nowcoder.com/questionTerminal/da4115e308c948169a9a73e50d09a3e7

下面是這個題目的解答:
每個li標籤的onclick事件執行時,本身onclick繫結的function的作用域中沒有變數i,i為undefined,則解析引擎會尋找父級作用域,發現父級作用域中有i,且for迴圈繫結事件結束後,i已經賦值為4,所以每個li標籤的onclick事件執行時,alert的都是父作用域中的i,也就是4。這是作用域的問題。

閉包只能取得包含函式中任何變數的最後一個值。因為別忘了閉包所儲存的是整個變數物件,而不是某個特殊的變數。

這是在迴圈體中建立閉包的常見錯誤。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures(一定要看這篇)

這裡面給onclick賦值的是閉包。很多人會問為什麼是閉包?之前閉包不是函式A裡的函式B嗎?函式B用來訪問函式A的變數,稱函式B是閉包,題目中只有一個函式為什麼也是閉包。其實,用兩個函式形成閉包只是一般形式。閉包真正的含義是,如果一個函式訪問了此函式的父級及父級以上的作用域變數,就可以稱這個函式是一個閉包。

<script>
    var a = 1;
    (function test (){
		alert(a);
	})()
</script>

所以上面的function都可以稱之為閉包(匿名閉包函式)。

這裡還是作用域的問題,那麼我們把每次的i都儲存到一個變數中,匿名閉包就可以實現想要的效果。

var elements=document.getElementsByTagName('li');
    var length=elements.length;
    for(var i=0;i<length;i++){
        elements[i].onclick=function(num){
        return function() {
                alert(num);
        };
    }(i);
    }

這樣就使用了閉包,這裡面的閉包指的是function() {alert(num);};第二個function裡面彈出的num是第一個function的引數,通過(i)執行了這裡面的第一個函式,同時i的值被儲存到num中。每個點選事件中都有一個區域性變數num,num儲存的是相應的i值。

第三部分:let的橫空出世

上面的牛客題目只需要將for(var i=0;i<length;i++)中的var改成let就能實現想要的效果,這讓在迴圈體內建立閉包具有更好的可讀性。let的簡單介紹:https://mp.csdn.net/postedit/81065540

let的到來,讓令人詬病的JS獲得了一絲生機,也補上了JS沒有塊級作用域的短板。ECMAScript6還有很多新特性,筆者也在不斷學習中。

第四部分:閉包的應用

函式工廠和閉包模擬私有方法

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures(這篇講得很好,希望讀者們讀透)

相關文章