讀《JavaScript進階程式設計》第7章有感。
一、究竟閉包是什嗎?
閉包是指有權訪問另一個函數範圍中的變數的函數。
個人感悟:
通過書中的這句定義,按中文文法分析,說白了,閉包就是一種函數,而這種函數可以訪問另一個函數範圍中的變數。
那為什麼這種函數有這樣牛B的功能呢?其實,它是利用了函數的範圍鏈。
二、建立閉包的常見方式:在一個函數內部建立另一個函數
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } };}
留意這個例子中標紅的地方。標紅處是一個內建函式的代碼,而這個內建函式正是匿名函數。
這兩行代碼訪問了外部函數中的變數propertyName。即使這個內建函式被返回了,而且是在其他地方被調用了,但它仍然可以訪問變數propertyName。
之所以還能夠訪問這個變數,是因為這個匿名函數的範圍鏈中包含了createComparisonFunction()的範圍(在範圍鏈中是以變數對象表示)
三、閉包能夠起什麼作用?為什麼閉包能夠起作用?
說到這裡,我們不得不回想一下之前學過的關於範圍鏈的知識。只有透徹理解範圍鏈,才能真正理解閉包起什麼作用,為什麼能夠起作用。
關於範圍鏈的知識,在我上一篇博文中已經詳細說明,在這就不重複敘述了,我只講關鍵的地方。
首先,當調用某個函數的時候,系統會為函數建立一個執行環境,並且通過複製函數的[[Scope]]屬性中的對象構建起執行環境的範圍鏈。而函數的使用中的物件作為變數對象被推入執行環境範圍鏈的最前端。
可見,當函數被調用時,會建立兩個東西,一個是函數的執行環境,另一個則是其相應的範圍鏈。
一般來說,當函數執行完畢後,局部使用中的物件就會被銷毀,記憶體中僅儲存全域範圍(也就是全域執行環境的變數對象)。
但是,閉包的情況有所不同。原因就是:
在另一個函數內部定義的函數會將包含函數(即外部函數)的使用中的物件添加到它的範圍鏈中。
回到上面的例子:
function createComparisonFunction(propertyName){ return function(object1,object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } };}
可以看出,在createComparisonFunction()函數內部定義的匿名函數的範圍鏈中,包含外部函數createComparisonFunction()的使用中的物件。
在匿名函數從createComparisonFunction()中被返回後,它的範圍鏈就變成了所示:
此時,匿名函數可以訪問在createComparisonFunction()中定義的所有變數。
更重要的是,當createComparisonFunction()函數執行完畢後,也就是返回了匿名函數後,它的使用中的物件也不會被銷毀,這是因為匿名函數的範圍鏈仍然在引用這個使用中的物件。
也就是說,當createComparisonFunction()函數返回後,它的執行環境的範圍鏈會被銷毀,但它的使用中的物件仍然被引用。根據JS的GC策略,因為該使用中的物件引用次數不為0,因此它會留在記憶體中,直到匿名函數被銷毀後,createComparisonFunction()的使用中的物件才會被銷毀。
四、閉包與變數
由上知,閉包是通過範圍鏈這種機制來實現的,因此,它也有著一個值得我們注意的副作用,那就是:
閉包只能取得包含函數中任何變數的最後一個值,因為閉包只是引用其範圍鏈上的變數對象。
例子不敲了,自己推敲一下吧哈~書上有,懶得敲了。
五、總結
由於閉包會攜帶包含它的函數的範圍,因此會比其他函數佔用更多的記憶體,過度使用閉包可能會導致記憶體佔用過多,這方面要注意一下。
這本JS基礎書真心好,閉包這部分得多看看。
關於閉包的一些應用將在下一篇文章講述。
共勉。