本文收集了多本書里對JavaScript閉包(Closure)的解釋,或許會對理解閉包有一定幫助。 ...
JavaScript閉包(Closure)
本文收集了多本書里對
JavaScript閉包(Closure)
的解釋,或許會對理解閉包有一定幫助。
《你不知道的JavsScript》
- JavaScript 中閉包無處不在,你只需要能夠識別並擁抱它。
- 閉包是基於詞法作用域書寫代碼時所產生的自然結果。
- 當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用域之外執行。
- 無論通過何種手段將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始定義作用域的引用,無論在何處執行這個函數都會使用閉包。
- 無論何時何地,如果將函數(訪問它們各自的詞法作用域)當作第一級的值類型併到處傳遞,你就會看到閉包在這些函數中的應用。在定時器、事件監聽器、 Ajax 請求、跨視窗通信、Web Workers 或者任何其它的非同步(或者同步)任務中,只要使用了回調函數,實際上就是在使用閉包。
《JavaScript編程解析》
1. 對閉包的初步認識
var fn = f(); // 將函數f 的返回值賦值給變數fn
fn(); // 1
fn(); //2
fn(); //3
function f() {
var cnt = 0;
return function() { return ++cnt; }
}
var fn1 = f1();
fn1(); //1
fn1(); //1
function f1(){
var cnt = 0;
return ++cnt;
}
從錶面上來看,閉包是一種具有狀態的函數。或者也可以將閉包的特征理解為,其相關的局部變數在函數調用結束之後將會繼續存在。
2. 閉包的原理
- 閉包的前提條件是需要在函數聲明的內部聲明另一個函數(即嵌套的函數聲明)。
- 閉包指的是一種特殊的函數,這種函數會在被調用時保持當時的變數名查找的執行環境。
- 閉包僅僅是保持了變數名查找的狀態,而並沒有保持對象所有的狀態,對此請加以區分。也就是說,閉包雖然會保持(在嵌套外層進行函數調用時被隱式地生成的)Call 對象,但無法保持 Call 對象的屬性所引用的之前的對象的狀態。
3. 防範命名空間的污染
- 模塊
- 避免使用全局變數
通過實現信息隱藏
//使用了閉包的模塊 // 在此調用匿名函數 // 由於匿名函數的返回值是一個函數,所以變數sum 是一個函數 var sum = (function() { // 無法從函數外部訪問該名稱 // 實際上,這變成了一個私有變數 // 一般來說,在函數被調用之後該名稱就將無法再被訪問 // 不過由於是在被返回的匿名函數中,所以仍可以繼續被使用 var position = { x:2, y:3 }; // 同樣是一個從函數外部無法被訪問的私有變數 // 將其命名為sum 也可以。不過為了避免混淆,這裡採用其他名稱 function sum_internal(a, b) { return Number(a) + Number(b); } // 只不過是為了使用上面的兩個名稱而隨意設計的返回值 return function(a, b) { print('x = ', position.x); return sum_internal(a, b); }; } )(); // 調用 sum(3, 4); x = 2 7
在利用函數作用域可以封裝名稱,以及閉包可以使名稱在函數調用結束後依然存在這兩個特性後,信息隱藏得以實現。
(function() { 函數體 })();
4.閉包與類
計數器功能的類
function counter_class(init) { // 初始值可以通過參數設定 var cnt = init || 0; // 設置預設參數的習慣做法(參見5.5 節) // 如有必要,可在此聲明私有變數與私有函數 return { // 公有方法 show:function() { print(cnt); }, up:function() { cnt++; return this; }, // return this 在使用方法鏈時很方便 down:function() { cnt--; return this; } }; } // 使用代碼 var counter1 = counter_class(); counter1.show(); 0 counter1.up(); counter1.show(); 1 var counter2 = counter_class(10); counter2.up().up().up().show(); // 方法鏈 13
表達式閉包
JavaScript 有一種自帶的增強功能,稱為支持函數型程式設計的表達式閉包(Expression closure)。
var sum = function(a, b) { return Number(a) + Number(b); } //可以省略為 var sum = function(a, b) Number(a) + Number(b);
5.閉包與回調函數
《JavaScript高級程式設計》
- 閉包是指有權訪問另一個函數作用域中的變數的函數。創建閉包的常見方式,就是在一個函數內部創建另一個函數
- 當某個函數被調用時,會創建一個執行環境(execution context)及相應的作用域鏈。
- 由於閉包會攜帶包含它的函數的作用域,因此會比其他函數占用更多的記憶體。過度使用閉包可能會導致記憶體占用過多,我們建議讀者只在絕對必要時再考慮使用閉包。
記憶體泄漏
由於IE9之前的版本對JScript對象和COM對象使用不同的垃圾收集常式,因此閉包在IE的這些版本中會導致一些特殊的問題。具體來說,如果閉包的作用域鏈中保存著一個HTML元素,那麼就意味著該元素將無法被銷毀。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
//把element變數設置為null。這樣就能夠解除對DOM對象的引用,順利地減少其引用數,確保正常回收其占用的記憶體。
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
《JavaScript設計模式與開發實踐》
1. 閉包更多作用
- 封閉變數
延續局部變數的壽命
//把img變數用閉包封閉起來,便能解決請求丟失的問題 var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
2. 閉包與記憶體管理
- 局部變數本來應該在函數退出的時候被解除引用,但如果局部變數被封閉在閉包形成的環境中,那麼這個局部變數就能一直生存下去。如果在將來需要回收這些變數,我們可以手動把這些變數設為null。
- 跟閉包和記憶體泄露有關係的地方是,使用閉包的同時比較容易形成迴圈引用,如果閉包的作用域鏈中保存著一些DOM節點,這時候就有可能造成記憶體泄露。但這本身並非閉包的問題,也並非JavaScript的問題。
- 如果要解決迴圈引用帶來的記憶體泄露問題,我們只需要把迴圈引用中的變數設為null即可。將變數設置為null意味著切斷變數與它此前引用的值之間的連接。當垃圾收集器下次運行時,就會刪除這些值並回收它們占用的記憶體。
《JavaScript權威指南》
- JavaScript也採用詞法作用域(lexical scoping),也就是說,函數的執行依賴於變數作用域,這個作用域是在函數定義時決定的,而不是函數調用時決定的。為了實現這種詞法作用域,JavaScript函數對象的內部狀態不僅包含函數的代碼邏輯,還必須引用當前的作用域鏈。函數對象可以通過作用域鏈相互關聯起來,函數體內部的變數都可以保存在函數作用域內,這種特性在電腦科學文獻中稱為“閉包”。
- 從技術的角度講,所有的JavaScript函數都是閉包:它們都是對象,它們都關聯到作用域鏈。
- 是如果這個函數定義了嵌套的函數,並將它作為返回值返回或者存儲在某處的屬性里,這時就會有一個外部引用指向這個嵌套的函數。它就不會被當做垃圾回收,並且它所指向的變數綁定對象也不會被當做垃圾回收。
轉載請註明出處:http://www.cnblogs.com/givebest/p/5617565.html