之所以再寫一篇閉包的文章,原因有二:一個是之前的文章寫的不是很清楚,比較含糊。之前文章請看js閉包文章一 。原因二是閉包確實是js的痛點和重點,不少網友也問過我,所以,決定再寫一篇關於閉包的文章。
案例
首先看下閉包的一個簡單案例
function A(){
function B(){
console.log("Hello haorooms!");
}
return B;
}
var c = A();
c();//Hello haorooms!
這個例子是一個比較簡單的閉包,解釋如下:
(1)定義了一個普通函數A
(2)在A中定義了普通函數B
(3)在A中返回B(確切的講,在A中返回B的引用)
(4)執行A(),把A的返回結果賦值給變數 c
(5)執行 c()
當一個內建函式被其外部函數之外的變數引用時,就形成了一個閉包。
閉包的作用
在上述例子中,B定義在A中,因此B依賴於A,而外部變數 c 又引用了B, 所以A間接的被 c 引用,也就是說,A不會被回收,會一直儲存在記憶體中。為了證明我們的推理,上面的例子稍作改進。也可以看我之前閉包文章中的例子:。
function A(){
var count = 0;
function B(){
count ++;
console.log(count);
}
return B;
}
var c = A();
c();// 1
c();// 2
c();// 3
count是A中的一個變數,它的值在B中被改變,函數B每執行一次,count的值就在原來的基礎上累加1。因此,A中的count一直儲存在記憶體中。
這就是閉包的作用,有時候我們需要一個模組中定義這樣一個變數:希望這個變數一直儲存在記憶體中但又不會“汙染”全域的變數,這個時候,我們就可以用閉包來定義這個模組。
常見的閉包寫法
前面我的文章中多次提及“自調用匿名函數”,
包裹方法如下:
(function(){
})()
用上面的這個包裹起來,就可以了。
但是還有一個問題,當我們研究大牛的代碼的時候,前面經常看到有“;”,那是為了避免代碼合并等不必要的錯誤。
例如,我們隨便定義一個函數:
var haoroomsblog=function(){
}
(function(){
})()
由於haoroomsblog這個函數後面沒有加分號,導致代碼出錯,為了避免這類情況的發生,通常這麼寫!
;(function(){
})()
把你的外掛程式程式碼封裝裹在上面裡面,就是一個簡單的外掛程式了。(注js外掛程式和jquery外掛程式都是如此)
還有一個問題
把你的外掛程式包裹在
;(function(){
})()
基本上可以說是完美了。但是為了讓你開發的外掛程式應用更加廣泛,相容性更加好,還要考慮到用外掛程式的人的一些特殊的做法,例如,有些朋友為了避免jquery和zeptojs衝突,將jquery的首碼“$”,修改為“jQuery”,還有些朋友將預設的document等方法修改。為了讓你的外掛程式在這些東西修了了的情況下照常運行,那麼我們的做法是,把程式碼封裝裹在如下裡面:
;(function($,window,document,undefined){
//我們的代碼。。
})(jQuery,window,document);
其實,大部分自調用匿名函數都是閉包的一種應用和寫法。
例如下面的例子
(function(document){
var viewport;
var obj = {
init:function(id){
viewport = document.querySelector("#"+id);
},
addChild:function(child){
viewport.appendChild(child);
},
removeChild:function(child){
viewport.removeChild(child);
}
}
window.jView = obj;
})(document);
以上代碼可以改寫成如下代碼:
var f = function(document){
var viewport;
var obj = {
init:function(id){
viewport = document.querySelector("#"+id);
},
addChild:function(child){
viewport.appendChild(child);
},
removeChild:function(child){
viewport.removeChild(child);
}
}
window.jView = obj;
};
f(document);
在這段代碼中似乎看到了閉包的影子,但 f 中沒有任何傳回值,似乎不具備閉包的條件,注意這句代碼:
window.jView = obj;
obj 是在 f 中定義的一個對象,這個對象中定義了一系列方法, 執行window.jView = obj 就是在 window 全域對象定義了一個變數 jView,並將這個變數指向 obj 對象,即全域變數 jView 引用了 obj 。而 obj 對象中的函數又引用了 f 中的變數 viewport ,因此 f 中的 viewport 不會被回收,會一直儲存到記憶體中,所以這種寫法滿足閉包的條件。 另外,我們把obj賦值給window.jView ,那麼,我們在整個window中可以直接調用obj 中的函數,但是函數內部的變數不會被收回,例如我們調用obj的init函數可以這麼寫:
window.jView.init("haorooms")
小結
以上是對閉包最簡單的理解。當然閉包還有更深入的理解。例如執行環境(execution context)、使用中的物件(activation object)以及範圍(scope)和範圍鏈(scope chain)的運行機制等等。當然,我們理解閉包先從簡單開始,寫的多了,理解的就越深了。本文也是對閉包的最簡單的入門,希望通過這篇文章,大家對閉包有了一定的理解。