過不少談如何理解閉包的方法,但大多數文章,都是照抄或者解釋《Javascript高級程序設計(第三版)》對於閉包的講解,甚至例程都不約而同的引用高程三181頁‘閉包與變量’一節的那個「返回數組各個項,結果各個項的值都相同」的例程,還有些文章的講解過程上一步與下一步之間的跨度簡直就是一步登天,讓人反覆看半天都沒法理解。javascript
閉包的理解須要不少概念作鋪墊,包括變量做用域鏈、執行環境、變量活動對象、引用式垃圾內存收集機制等,若是對本文涉及的這些概念不理解,能夠去找本《Javascript高級程序設計(第三版)》好好看看並理解這些概念。java
我本身也是在反覆編寫一些例子並仔細認真的閱讀和理解高程三與閉包相關的概念和知識後,終於對閉包有了必定的理解,爲了檢驗學習效果,特別寫篇文章以便檢驗本身是否真正理解和掌握。數組
在理解閉包以前,咱們先來看些理解閉包容易忽略的小東西。閉包
//例子1:
function foo(){ var a = 1; return a; }; console.log(foo()); //1
上面的這個例子很好理解,一個函數若是return一個值,那麼這個函數就能夠看成return的值直接使用,不管它return的是一個引用類型(函數、數組)仍是基本數據類型。函數
//例子2:
function foo(){ function foos(){ var b = 2; return b; } }; console.log(foos()); //Uncaught ReferenceError: foos is not defined
這個例子說明,若是一個函數B被聲明在一個函數A的內部,那麼在函數A外部,經過函數B的函數名直接調用函數B,是會出未定義錯誤的。你能夠理解爲:變量做用域規則中的局部做用域規則對於函數聲明一樣成立。學習
//例子3:
function foo(){ var a = 1; function foos(){ return a;//注意:返回的a是foo中定義的變量,而不是foos中定義的變量。 } console.log(foos()); }; foo();//1
例子3說明foos只能在foo內部執行,這符合咱們對於「變量做用域規則中的局部做用域規則,對於函數聲明一樣成立」的理解。spa
//例子4:
function foo(){ var arr = new Array();
for(var i=0;i<10;i++){ arr[i] = function(){ return i; }; } return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]()); } }; }; console.log(foo()());//輸出10個10;
例子4中中執行foo()之後獲得的是foo本身返回的一個匿名函數(以下所示):設計
return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]()); } };
那麼,此時咱們還須要執行一遍這個獲得的匿名函數,才能得出最後的值(也就是:10個10),而執行這個獲得的匿名函數(咱們不妨叫它函數C)時,foo已經執行了一遍了,這個時候foo的變量i的值已是10了,因爲變量對象是經過引用賦值的,因此這個時候foo內的閉包函數(也就是引用了foo的變量i的匿名函數)再來引用foo的變量i,那麼獲得的值天然就是10了。這裏的關鍵在與foo內的閉包函數執行的時機,若是閉包函數當即執行後再賦值給arr[i],那麼就能夠獲得咱們指望的值0到9了;code
//例子5:
function foo(){ var arr = new Array(); for(var i=0;i<10;i++){ arr[i] = function(){ return i; }(); } return function(){ for(var k = 0;k<arr.length;k++){ console.log(arr[k]); } }; }; console.log(foo()());//0到9
關於例子4更詳細的解釋:
在例子4中,根據javascript的引用垃圾收集機制(這個機制的規則是:存在大於0個引用的變量不該該被銷燬),雖然此時函數foo已經執行完了,可是foo的這個變量i由於被匿名函數(咱們不妨叫它函數B)引用,因此這個i仍是沒有被垃圾收集程序銷燬,那麼這個i此時存在哪裏呢?確定不是存在於函數foo的執行環境裏面,由於執行環境建立和銷燬的規則是:當一個函數建立時,則建立這個函數的執行環境,一旦這個函數執行完畢則立刻銷燬這個執行環境。所以這個i只可能存在於foo內部的匿名函數(函數B)的執行環境中,這個函數B的執行環境中不但有本身的做用域和本身的變量對象,並且包含了其外部函數foo的做用域和foo的變量對象,變量i就存在於函數B包含的foo的做用域中和變量對象中。因爲javascript不能直接操做內存空間,因此javascript只能以引用的方式訪問變量對象,當foo的變量i的值已經變成10了的時候,再去執行foo的閉包函數(函數B),那麼此時對i的引用天然會獲得10。對象
使用以上實驗得出的原理,咱們還能夠很容易的理解《Javascript高級程序設計(第三版)》181頁最下面的那個例子:
//《Javascript高級程序設計(第三版)》181頁最下面的那個例子: function createFunctions(){ var result = new Array(); for(var i=0;i<10;i++){ result[i] = function(num){ return function(){ return num; } }(i);//這裏是將參數傳給該匿名函數,而且當即執行該匿名函數的寫法; } return result; } for(var i=0;i<10;i++){ console.log(createFunctions()[i]()); }//0,1,2,3,4,5,6,7,8,9
同時,咱們能夠很容易的改寫這個例子:
function createFunctions(){ var result = new Array(); for(var i=0;i<10;i++){ result[i] = function(){ return function(){ return i; }()//這裏添加了當即執行 }();//這裏是將參數傳給該匿名函數,而且當即執行該匿名函數的寫法; } return result; } for(var i=0;i<10;i++){ console.log(createFunctions()[i]);//這裏去掉了一對括號 }//0,1,2,3,4,5,6,7,8,9
這個改寫的例子同時證實,最內層的匿名函數仍是能夠引用最外面全局函數的變量。
下面這張圖片中的例子,使用閉包就能夠很好的理解了:
要是實在不理解,還能夠試着這樣來驗證一下幫助理解:
function foo(){ var diss = "mem_count"; return function(){ return diss; } } var fo = foo(); var as = fo(); //undefined as //"mem_count" var as2 = fo(); //undefined as2 //"mem_count" fo() //"mem_count" fo() //"mem_count" fo() //"mem_count"
若是這樣仍是沒辦法理解,那真是應該先去看看變量的做用域、垃圾回收機制,函數表達式等基礎知識了。