對於新手來說,閉包的概念往往有些晦澀。書上的概念描寫的非常簡短,網上的各類文章又常常是長篇大論地探討閉包的作用呀,影響呀,看到最后,覺得是越看越暈,越來越搞不懂什么是閉包了,所以在這里分享自己對閉包的淺顯的理解,希望對新手有所幫助,相互交流。
>>進入主題
什么是閉包:閉包是指有權訪問另一個函數作用域的變量的函數。--《javascript高級程序設計》
書上的概念就這么一句話,其實閉包就是像這句話定義的一樣簡單。如果有一個函數fun2,它可以訪問在其它函數如fun1中的局部變量,那么它(fun2)就是閉包。創建閉包的簡單方式,就在在函數內部創建另一個函數,下面例子創建了個簡單的閉包
1 function fun1 () { 2 var a = 0; 3 function fun2 () { 4 console.log(a); // 在這個函數fun2中可以訪問另一個函數中的變量a,所以fun2()就是一個閉包。 5 } 6 fun2(); 7 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~定義分割線~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
以下是關於閉包的一些知識與注意事項:
1.在定義函數外調用閉包的方法,逃離方式:
(1).將內部函數指定給一個全局變量;
var globalVar; function outer() { console.log('outer'); function inner(){ console.log('inner'); } globalVar = inner; } outer(); // outer globalVar(); // inner;
在這個例子中inner()通過全局變量的引用成功逃離,現在可以在全局中調用,而且可以引用outer()的變量
(2).通過返回值來'營救'內部函數的引用
function outer() { console.log('outer'); function inner(){ console.log('inner'); } return inner; } var fn = outer(); // outer fn(); // inner;
在這個例子中inner()通過返回值成功逃離,現在可以在全局中調用,而且可以引用outer()的變量
2.在函數外調用閉包的影響:增加內存占用;
本來正常的情況下是函數調用結束之后函數的執行環境離開環境棧,定義的變量廢棄(廢棄與垃圾收集機制有關),活動變量(變量對象)會被銷毀,內存釋放。但是現在因為閉包的作用域鏈包含了外部函數的變量對象,外部函數的變量有可能再被引用,垃圾收集機制不會將外部函數的變量廢棄,在內存保留的外部函數的變量對象。這樣就加大了對內存的占用。
3.閉包與變量的關系:閉包中常見的誤區及解決技巧
閉包保存的是包含函數的整個變量對象,所取得的外部對象變量為閉包被調用時刻的對象變量,一般為外部函數變量的最后一個值。
例:
function createFun() { var result = []; for ( var i = 0; i < 10; i++) { result[i] = function() { return i; }; } return result; } var result = createFun(); console.log(result[5]()); // 10
這里外部函數的返回值為一個數組,數組值為不同函數(閉包)的引用,我們會誤認為每個閉包的調用的返回值不同,但實際上每個函數都會返回一樣的值。因為當閉包調用時,調用閉包的外部函數已經執行完畢,此時外部函數的變量對象中的 i = 10,而我們閉包的返回值為i,閉包會獲取調用時的外部變量對象,此時的i為10。
解決:
function createFun() { var result = []; for ( var i = 0; i < 10; i++) { result[i] = function(num) { return function() { return num; }; }(i); } return result; } var result = createFun(); console.log(result[5]()); // 5
在循環中,我們定義了一個匿名數組,並將立即執行該匿名函數的結果賦給數組,這里的匿名函數有一個參數num,每次將i作為參數傳遞給num,每次循環num都會得到不同的值,所以每次返回了不同的函數(區別在於num值不同),當在外部調用數組值時,會返回不同的值,與預期相符。
4. 注意閉包中this值
首先,關於函數中this指向,我們應該知道this指向調用該函數的對象,若無明確調用對象則指向window對象。
在閉包中容易弄錯的this指向,例:
var name = "window"; var o = { name: "object", getName: function() { return function() { return this.name; }; } }; console.log(o.getName()()); // window
可以看出閉包this指向了全局對象,分析,可以把o.getName()()寫成(o.getName())(),這個表達式相當於第一步先執行了o.getName(),這個函數返回了一個匿名函數(閉包),然后在全局下執行了這個閉包,並不是通過對象o調用,所以this指向全局對象。
5. 內存泄漏的問題,如何減少不必要的內存占用
function assignHandler() { var ele = documnet.getElementById("somenode"); ele.onclick = function() { console.log(ele.id); }; }
在上面的例子中定義ele的方法與匿名函數有關,於是ele保存了對匿名函數的引用,而閉包會引用包含函數也引用了ele對象,這樣就造成了對象的循環引用,ele元素(dom元素占用內存較大)就一直保存在了內存中無法釋放,解決方法如下:
function assignHandler() { var ele = documnet.getElementById("somenode"); var id = ele.id; ele.onclick = function() { console.log(id); // 通過id值中介表面上解除了與ele的循環引用 }; ele = null; // 手動解除引用 }
因為閉包會引用包含函數的整個變量對象,所以用id值只能表面上解除循環引用,需要進一步手動設置元素為null才能真正減少引用,釋放內存。
以上大多為使用閉包應該注意的事項,當然使用閉包也有很多優點,如:使用匿名函數模仿塊級作用域,定義訪問私有變量的特權方法,避免全局變量污染等,大家可以自行研究。