閉包是一個比較抽象的概念,水有點深,很容易繞進去,爲了方便本身總結,先把從書本《javascript權威指南》中學到的一些點放在博客上面。javascript
閉包是不少語言都具有的特性,在js中,閉包主要涉及到js的幾個其餘的特性:做用域鏈,垃圾(內存)回收機制等等。java
在js中,函數做用域是說變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。這與C/C++等的塊級做用域有所不一樣,因此你們在js中不要認爲一個對象也是一個做用域。數組
還有很重要的一點就是聲明提早,只要經過var 定義了變量,不論是否賦值,這個變量始終存在於這個函數體內部。閉包
1 |
var scope="global"; |
1 |
var scope="global"; |
2、做用域鏈 函數
做用域鏈圖中很明確的表示出:在變量解析過程當中首先查找局部的做用域,而後查找上層做用域。ui
在代碼一的函數當中沒有定義變量i,因而查找上層做用域(全局做用域),進而進行輸出其值。可是在代碼二的函數內定義了變量i(不管是在alter以後仍是以前定義變量,都認爲在此做用域擁有變量i),因而再也不向上層的做用域進行查找,直接輸出i。可是不幸的是此時的局部變量i並無賦值,因此輸出的是undefined。spa
代碼一3d
1 |
var i = 10; |
代碼二指針
1 |
var i=10; |
瞭解了做用域鏈,咱們再來看看js的內存回收機制,通常來講,一個函數在執行開始的時候,會給其中定義的變量劃份內存空間保存,以備後面的語句所用,等到函數執行完畢返回了,這些變量就被認爲是無用的了,對應的內存空間也就被回收了。code
下次再執行此函數的時候,全部的變量又回到最初的狀態,從新賦值使用。可是若是這個函數內部又嵌套了另外一個函數,而這個函數是有可能在外部被調用到的.而且這個內部函數又使用了外部函數的某些變量的話.這種內存回收機制就會出現問題.若是在外部函數返回後,又直接調用了內部函數,那麼內部函數就沒法讀取到他所須要的外部函數中變量的值了。因此js解釋器在遇到函數定義的時候,會自動把函數和他可能使用的變量(包括本地變量和父級和祖先級函數的變量(自由變量))一塊兒保存起來,也就是構建一個閉包,這些變量將不會被內存回收器所回收,只有當內部的函數不可能被調用之後(例如被刪除了,或者沒有了指針),纔會銷燬這個閉包,而沒有任何一個閉包引用的變量纔會被下一次內存回收啓動時所回收。
個人理解是,閉包就是可以讀取其餘函數內部變量的函數。
因爲在Javascript語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。
因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。
1 |
function f1(){ |
在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證實了,函數f1中的局部變量n一直保存在內存中,並無在f1調用後被自動清除。
爲何會這樣呢?緣由就在於f1是f2的父函數,而f2被賦給了一個全局變量,這致使f2始終在內存中,而f2的存在依賴於f1,所以f1也始終在內存中,不會在調用結束後,被垃圾回收機制(garbage collection)回收。
這段代碼中另外一個值得注意的地方,就是「nAdd=function(){n+=1}」這一行,首先在nAdd前面沒有使用var關鍵字,所以 nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個
匿名函數自己也是一個閉包,因此nAdd至關因而一個setter,能夠在函數外部對函數內部的局部變量進行操做。
接下來的兩個例子是告訴咱們一種避免使用閉包的方法。例子2是一個閉包,因爲閉包使js的GC不會回收變量i,由於變量result的執行依賴於函數foo中的局部變量i。因爲函數foo執行的時候,只是定義了result這個數組函數,因此最後的輸出結果是三、三、3。
對比例子3,因爲result這個數組是一個沒有自執行的有傳參的匿名函數,儲存的是有傳參的匿名函數(傳入了當前的i),因此輸出的結果是0、一、2。
1 |
var result=[]; |
result[0] = result[1] = result[2] = function(){ alert(i) }
例子3
1 |
var result=[]; |
result[0] = result[1] = result[2] = function(){ alert(j) }