閉包的作用與可能引起的記憶體洩漏
1.作用域鏈
理解閉包之前需要明白一個概念:__作用域鏈__。當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈。作用域鏈的用途,是保證對執行環境有權訪問的所有變數和函式的有序訪問。作用域的前端,始終都是當前執行的程式碼所在環境的變數物件。如果這個環境是函式,則將其活動物件作為__變數物件__。這個變數物件來自於下一個包含環境,下一個變數物件又來自於下一個包含環境,知道全域性執行環境。全域性執行環境的變數物件始終都是作用域鏈的最後一個物件。
var name = 'Titan'; function sayHi(){ alert('Hi,'+name); } sayHi(); // "Hi,Titan"
函式sayHi()的執行環境為全域性環境,所以它的變數物件為window。當函式執行到name時,先查詢區域性環境,找到則返回;否則順著作用域鏈查詢,在全域性環境中找到變數name返回。這樣一個查詢變數的有序過程的依據就是作用域鏈。
2.區域性變數
我們都知道當一個函式被執行之後,之前函式內的區域性變數會被銷燬。而全域性變數是始終不會銷燬的,當我們想要在全域性環境始終保持一個變數(比如session)的時候往往會將它定義為全域性變數,但是這樣做的危險就是汙染了全域性變數。這時的做法往往就是建立一個函式,將想要儲存的變數閉包起來,使其不汙染全域性變數。但是問題又來了,如果用函式閉包來儲存變數即為區域性變數,當函式被執行後這個區域性變數就會被銷燬,怎麼達到儲存變數的效果?
3.巢狀函式
為了達到目的,注意神奇的時刻來了,我們可以在閉包的函式中再建立一個函式(匿名函式就可以,因為函式名不會用到)。這樣我們在操作需要儲存的變數的時候只要執行這個巢狀的匿名函式,就可以達到儲存的效果,而不用怕呼叫外部函式後會銷燬變數。
var outter = (function(){
var id = 0;
return function inner(){
return id++;
};
})();
alert(outter()); // 0
alert(outter()); // 1
以上程式碼說明,變數id為我們想儲存的id。操作id的時候,我們呼叫outter(),實際是呼叫了巢狀函式inner()。inner()在執行時,根據作用域鏈找到id並修改它。當然inner()在執行後銷燬。但是對於id來說,它的包含環境為outter,outter並未執行所以id不會被銷燬。這樣id就被儲存下來了。
4.記憶體洩漏
當然,閉包的作用域鏈中儲存的元素,該元素將無法被銷燬,在垃圾回收時不會被收回。如果儲存元素為一個引用變數,而且不是必須要儲存的,那麼它也會因此被儲存下來佔據大量的記憶體,造成記憶體洩漏。所以當閉包作用域鏈中儲存的引用變數不需要的時候,應設定為null,解除引用確保正常回收其佔用的記憶體。
function assignHandler(){
var element = $('id');
var id = elment.id;
element.onclick = function(){
alert(id);
};
element = null;
}
5.閉包的作用
總之,閉包最大的作用就是可以利用作用域鏈訪問到區域性變數,通過呼叫巢狀匿名函式可以把外圍作用域中的變數值儲存在記憶體中而不在函式呼叫(實際呼叫的為巢狀匿名函式,不是外圍函式)完畢後就銷燬。當然使用不當會造成記憶體洩漏等問題,所以使用謹慎使用。閉包很難理解,但是又非常有用。才疏學淺,個人理解可能有誤。如有錯誤,歡迎討論。
歡迎光臨我的個人部落格:傳送門