本文收集了多本書裏對
JavaScript閉包(Closure)
的解釋,或許會對理解閉包有必定幫助。編程
JavaScript 中閉包無處不在,你只須要可以識別並擁抱它。設計模式
閉包是基於詞法做用域書寫代碼時所產生的天然結果。閉包
當函數能夠記住並訪問所在的詞法做用域時,就產生了閉包,即便函數是在當前詞法做用域以外執行。異步
不管經過何種手段將內部函數傳遞到所在的詞法做用域之外,它都會持有對原始定義做用域的引用,不管在何處執行這個函數都會使用閉包。函數
不管什麼時候何地,若是將函數(訪問它們各自的詞法做用域)看成第一級的值類型並處處傳遞,你就會看到閉包在這些函數中的應用。在定時器、事件監聽器、 Ajax 請求、跨窗口通訊、Web Workers 或者任何其它的異步(或者同步)任務中,只要使用了回調函數,實際上就是在使用閉包。this
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; }
從表面上來看,閉包是一種具備狀態的函數。或者也能夠將閉包的特徵理解爲,其相關的局部變量在函數調用結束以後將會繼續存在。設計
閉包的前提條件是須要在函數聲明的內部聲明另外一個函數(即嵌套的函數聲明)。code
閉包指的是一種特殊的函數,這種函數會在被調用時保持當時的變量名查找的執行環境。對象
閉包僅僅是保持了變量名查找的狀態,而並無保持對象全部的狀態,對此請加以區分。也就是說,閉包雖然會保持(在嵌套外層進行函數調用時被隱式地生成的)Call 對象,但沒法保持 Call 對象的屬性所引用的以前的對象的狀態。事件
模塊
避免使用全局變量
經過實現信息隱藏
//使用了閉包的模塊 // 在此調用匿名函數 // 因爲匿名函數的返回值是一個函數,因此變量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() { 函數體 })();
計數器功能的類
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);
閉包是指有權訪問另外一個函數做用域中的變量的函數。建立閉包的常見方式,就是在一個函數內部建立另外一個函數
當某個函數被調用時,會建立一個執行環境(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; }
封閉變量
延續局部變量的壽命
//把img變量用閉包封閉起來,便能解決請求丟失的問題 var report = (function(){ var imgs = []; return function( src ){ var img = new Image(); imgs.push( img ); img.src = src; } })();
局部變量原本應該在函數退出的時候被解除引用,但若是局部變量被封閉在閉包造成的環境中,那麼這個局部變量就能一直生存下去。若是在未來須要回收這些變量,咱們能夠手動把這些變量設爲null。
跟閉包和內存泄露有關係的地方是,使用閉包的同時比較容易造成循環引用,若是閉包的做用域鏈中保存着一些DOM節點,這時候就有可能形成內存泄露。但這自己並不是閉包的問題,也並不是JavaScript的問題。
若是要解決循環引用帶來的內存泄露問題,咱們只須要把循環引用中的變量設爲null便可。將變量設置爲null意味着切斷變量與它此前引用的值之間的鏈接。當垃圾收集器下次運行時,就會刪除這些值並回收它們佔用的內存。
JavaScript也採用詞法做用域(lexical scoping),也就是說,函數的執行依賴於變量做用域,這個做用域是在函數定義時決定的,而不是函數調用時決定的。爲了實現這種詞法做用域,JavaScript函數對象的內部狀態不只包含函數的代碼邏輯,還必須引用當前的做用域鏈。函數對象能夠經過做用域鏈相互關聯起來,函數體內部的變量均可以保存在函數做用域內,這種特性在計算機科學文獻中稱爲「閉包」。
從技術的角度講,全部的JavaScript函數都是閉包:它們都是對象,它們都關聯到做用域鏈。
是若是這個函數定義了嵌套的函數,並將它做爲返回值返回或者存儲在某處的屬性裏,這時就會有一個外部引用指向這個嵌套的函數。它就不會被當作垃圾回收,而且它所指向的變量綁定對象也不會被當作垃圾回收。