淺談JavaScript閉包
一、背景知識
在介紹閉包之前,我覺得有必要先簡單的介紹一些背景知識,如變量的作用域、嵌套函數、垃圾回收機制等概念。
1、作用域
作用域是程序運行時變量可被訪問的範圍,定義在函數內的的變量是局部變量,局部變量的作用域只能是函數內部範圍內,它不能在函數外引用。定義在模塊最外層的的變量是全局變量,它是全局範圍內可見的,當然在函數裏面也可以讀取到全局變量的。
var a = 123; //全局變量 function fun(){ var b = 456; //局部變量 }
2、嵌套函數
函數不僅可以定義在模塊的最外層,還可以定義在另外一個函數的內部,像這種定義在函數裏面的函數稱之為“嵌套函數”。如下所示:
1 function foo(){
2 function goo() {
3 alert(123);
4 }
5 goo();
6 }
3、垃圾回收機制GC
JavaScript具有自動垃圾回收機制(GC:Garbage Collecation),也就是說,執行環境會負責管理代碼執行過程中使用的內存。原理:垃圾收集器會定期(周期性)找出那些不在繼續使用的變量,然後釋放其內存。
GC在回收內存時,首先會判斷該對象是否被其它對象引用。在確定沒有其它對象引用便釋放該對象內存區域。
二、什麽是閉包Closure
關於閉包,“官方”的解釋是:閉包是一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。
其實,理解閉包的含義並不難。接下來,我將用JavaScript創建一個閉包來幫助你理解什麽是閉包,因為作為程序員,有時候,看代碼來理解一個知識點會比你看文字定義來理解更加容易。
function A(){ var foo = 123; function B(){ alert(foo); } return B; } var c = A(); c();
上面代碼翻譯成自然語言如下:
(1)定義了一個普通函數A
(2)在A中定義了普通函數B
(3)在A中返回B(確切的講,在A中返回B的引用)
(4)執行A(),並把A的返回值賦給變量 c
(5)執行 c()
把上面這五個步驟總結一下就是:函數A的內部函數B被函數A外的一個變量 c引用;
把這句再加工一下就變成了閉包的定義:當一個內部函數被其外部函數之外的變量引用時,就形成了一個閉包。
也可以這樣說:閉包是指有權訪問另一個函數作用域中的變量的函數。
三、閉包的作用
其實,閉包與普通函數的差別在於局部變量可以在函數執行結束後仍然被函數外的代碼訪問。這意味著函數必須返回一個指向閉包的“引用”,或將這個“引用”賦值給某個外部變量,才能保證閉包中局部變量被外部代碼訪問。當然包含這個引用的實體應該是一個對象,因為在Javascript中除了基本類型剩下的就都是對象了。
簡而言之,閉包的作用就是在A執行完並返回後,閉包使得Javascript的垃圾回收機制GC不會收回A所占用的資源,因為A的內部函數B的執行需要依賴A中的變量。
所以,當我們需要在一個模塊中定義一個能一直保存在內存中但又不會“汙染”全局的變量的時候,我們就可以用閉包來定義這個模塊。
四、閉包的應用場景
1、保護函數內的變量安全。以上面第三個例子為例,函數A中foo只有函數B才能訪問,而無法通過其他途徑訪問到,因此保護了f的安全性。
2、在內存中維持一個變量。依然如前例,由於閉包,函數A中foo的一直存在於內存中,因此每次執行c(),都會彈出內容為“123”的彈出框。
以上兩點是閉包最基本的應用場景,很多經典案例都源於此。
五、閉包為什麽不會被JS垃圾回收機制回收
在JavaScript中,如果一個對象不再被引用,那麽這個對象就會被垃圾回收機制GC回收。如果兩個對象互相引用,而不再被第3者所引用,那麽這兩個互相引用的對象也會被回收。
以上面第三個例子為例:因為函數A被函數B引用,函數B又被函數A外的變量c引用,這也就是為什麽函數A執行後不會被回收的原因。
六、總結
以上就是對閉包簡單的理解,其實,關於閉包的知識點還遠不止上述所講到的這些,要想更深入的去了解閉包,就回涉及到JS的執行環境(execution context)、活動對象(activation object)以及作用域(scope)和作用域鏈(scope chain)的運行機制等這些概念。但是,作為一位初學者,其實暫時可以不必了解這些的。
當然,如果你還想對閉包有一個進一步的了解,可以去看一下我的另一篇博客“深入理解JavaScript閉包”。
參考資料:http://blog.csdn.net/hitman9099/article/details/3854171
淺談JavaScript閉包