前端初學者在學習時都會遇上一個很頭疼的問題-----閉包
那么什么是閉包?
官方的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
廣義上的閉包就是指一個變量在它自身作用域的被使用了,就叫發生了閉包。粗魯地理解:閉包就是能夠讀取其它函數內部變量的函數。 在js中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單粗暴地理解成“定義在一個函數內部的函數”,即一個函數嵌套了另一個函數。
閉包是很多語言都具備的特性,在js中,閉包主要涉及到js的幾個其他的特性:
作用域鏈,垃圾(內存)回收機制,函數嵌套......
變量的作用域
Js中變量的作用域分兩種:全局變量和局部變量。
函數內部可以直接讀取全局變量。
1 var n = 'RORO彥'; 2 function f1() { 3 console.log(n); 4 } 5 f1();
在函數外部自然無法讀取函數內的局部變量。
1 function f1(){ 2 var n= 'RORO彥'; 3 } 4 console.log(n); // error
此外,函數內部聲明變量時,一定要用var關鍵字來命名變量。否則,就聲明了一個全局變量。
當我們需要從外部讀取局部變量,得到函數內的局部變量,可以在函數的內部再定義一個函數。
1 function f1(){ 2 n = 'RORO彥'; 3 function f2(){ 4 console.log(n); 5 } 6 }
如上,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2是可見的。反之,不行。這就是Javascript語言特有的“鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。
現在f2可以讀取f1中的局部變量,那么只要把f2作為返回值,我們就可以在f1外部讀取它的內部變量
1 function f1(){ 2 n = 'RORO彥'; 3 function f2(){ 4 console.log(n); 5 } 6 return f2; 7 } 8 var result=f1(); 9 result(); // RORO彥
作用
1.讀取函數內部的變量
2.令這些變量的值始終保持在垃圾(內存)回收機制中。
1 function f1(){ 2 var n = 'RORO彥'; 3 add=function(){n = 'RORO';}; 4 function f2(){ 5 console.log(n); 6 } 7 return f2; 8 } 9 var result=f1(); 10 result(); // RORO彥 11 add(); 12 result(); // RORO
在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次是RORO彥,第二次是RORO。函數f1中的局部變量n一直保存在內存中,並沒有在f1調用后被自動清除,而是保存在內存堆(heap)里, 原因是它被包裝或封裝在一個函數體內,這些構造器都被稱為閉包。它返回調用函數的運行結果,是函數本身。此外,add的值是一個匿名函數,它本身也是一個閉包,所以add相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。
注意事項
函數中的變量都保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。閉包會在父函數外部改變父函數內部變量的值。所以,當你把父函數當作對象使用,把閉包當作它的公用方法,把內部變量當作它的私有屬性,不要隨便改變父函數內部變量的值。
閉包與抽象數據類型
我們通過閉包能簡單地引入抽象數據類型。
例如,通過閉包實現一個 堆棧。
function createStack() { var elements = []; return { push: function(el) { elements.unshift(el); }, pop: function() { return elements.shift(); } }; } var stack = createStack(); stack.push(3); stack.push(4); stack.pop(); // 4
閉包與面向對象編程
在 JavaScript 中,閉包不是堆棧數據類型的最佳實現方式。用原型 Prototype 實現對內存更友好,在當前對象實例找不到相應屬性或方法時,會到相應實例共同引用的 Prototype 屬性尋找相應屬性或方法。如果在當前Prototype屬性找不到時,會沿着當前原型鏈向上查找,而Prototype 上的屬性或方法是公用的,而不像實例的屬性或方法那樣,各自單獨創建屬性或方法,從而節省更多的內存。
上述構造器看起來非常像類、對象、實例值和私有/公有方法。閉包與類相似,都會將一些能操作內部數據的函數聯系在一起。於是,我們可以像使用對象一樣使用閉包。當我們想在 JavaScript 創建“真正的”隱藏域,或者需要創建簡單的構造器時,我們可以優先使用閉包。不過對於一般的類來說,閉包可能還是有點太繁重了。
注:參考資料《javascript權威指南》第6版中文版