標籤:
閉包是指有權訪問另一個函數範圍中變數的函數。 建立閉包的常見方式,就是在一個函數內部建立另一個函數。本質上講,閉包就是講函數內部和函數外部串連起來的一座橋樑。
function a(){ var i = 0; function b(){ alert(++i); } return b;}var c = a();c(); //1
在函數a 中嵌套了函數b,並將函數b返回。
在執行完 var c = a() 後,變數c實際上指向了函數b,再執行c()後就會彈出一個視窗顯示i的值。
當函數a的內建函式b被函數a外的一個變數引用時,就建立了一個閉包。
閉包主要涉及到js的幾個其他的特性: 範圍鏈,垃圾(記憶體)回收機制,函數嵌套
function compare(value1, value2){ if(value1 > value2){ return 1; }else if(value1 < value2){ return -1; }else{ return 0; }}var result = compare(5,10);
1. 範圍鏈:
範圍鏈是在函數定義時建立的,當函數需要查詢一個變數的值的時候,js解析器會去範圍鏈中尋找。在上述例子中,尋找i時,會先在函數b中找,找到則返回,沒找到繼續在上一級的鏈即函數a中尋找,如果還沒找到,再到全域鏈中找,如果全域鏈中也沒有,則返回undefined。
在建立函數時,會建立一個預先包含全域變數對象的範圍鏈,這個範圍鏈被儲存在內部的[[Scope]]屬性中。 當調用函數時,會為函數建立一個執行環境,然後通過複製函數[[Scope]]屬性中的對象構建起執行環境的範圍鏈。
在上面的例子中,對於compare()函數的執行環境而言,其範圍中包含兩個變數對象: 本地使用中的物件和全域變數對象。範圍鏈本質上是一個指向變數對象的指標表,它只引用但不實際包含變數對象。
在函數中訪問一個變數時,會從範圍鏈中搜尋具有相應名字的變數。一般來講,當函數執行完畢後,局部使用中的物件就會被銷毀,記憶體中僅儲存全域範圍。但閉包的情況又有所不同。
2.記憶體回收機制:
一個函數在執行開始的時候,會給其中定義的變數劃分記憶體空間,用來儲存這些變數。等函數執行完畢後,這些變數的記憶體就會被回收。下次再執行時,又會重新分配。
如果一個函數的內部嵌套了另外的一個函數,內建函式有可能在外部被調用到,並且這個內建函式使用了外部函數的某些變數。
這種情況下,js解譯器在遇到函數定義的時候,會自動把函數和它可能用到的變數(包括本地變數、父級和祖父級函數的變數)一起儲存下來,也就是構建一個閉包。
這些變數將不會被記憶體回收器回收。只有當內部的函數不可能被調用以後,才會銷毀這個閉包。
3.閉包與變數:
閉包只能取得包含函數中任何變數的最後一個值。閉包所儲存的是整個變數對象,而不是某個特殊的變數。
var result = new Array();function createFunctions(){ for(var i=0; i<10; i++){ result[i] = function(){ return i; } } return result;}createFunctions();alert(result[0]()); //10alert(result[9]()); //10
這個函數會返回一個函數數組,表面上看,似乎每個函數都應返回自己的索引值,但實際中,每個函數都返回10。
因為每個函數的範圍鏈中都儲存著createFunctions()函數的使用中的物件,所以他們引用的都是同一個變數 i。當createFunctions()函數返回後,變數i的值時10,
此時,每個函數都引用著儲存變數i的同一個變數對象,所以在每個函數內部 i 的值都是10。
我們可以建立另一個匿名函數強制讓閉包的行為符合預期。
var result = new Array();function createFunctions(){ for(var i=0; i<10; i++){ result[i] = function(num){ return function(){ return num; } }(i); } return result;}createFunctions();alert(result[0]()); //0alert(result[9]()); //9
在上述代碼中,沒有直接把閉包賦值給數組,而是定義了一個匿名函數,並將立即執行該匿名函數的結果賦值給數組。這裡的匿名函數有一個參數num,也就是最終函數要返回的值。在調用匿名函數時,我們傳入了參數 i 。由於函數參數是按值傳遞的,所以就會將變數 i的值複製給參數num。而在這個匿名函數內部,又建立並返回了num的閉包。這樣一來,result數組的每個函數都有自己 num變數的一個副本,因此就可以返回各自不同的數值了。
4.關於this對象:
在閉包中使用this對象也會導致一些問題。this對象是在運行時基於函數的執行環境綁定的: 在全域函數中,this等於window,而當函數被作為某個對象的方法調用時,this等於當前對象。 不過,匿名函數的執行環境具有全域性,因此其this對象通常指向window。但有時候,由於編寫閉包的方式不同,這一點可能不會那麼明顯:
var name = "The Window";var object = { name: "My Object", getNameFunc: function(){ return function(){ this.name; }; }};alert(object.getNameFunc()()); //The Window
以上代碼先建立了一個全域變數name,又建立了一個包含name屬性的對象。這個對象還包含一個方法 -- getNameFunc(), 它返回一個匿名函數,而匿名函數又返回this.name。由於getNameFunc()返回一個函數,因此調用object.getNameFunc()()就會立即調用它返回的函數,結果就是返回一個字串。然而這個結果是全域name變數的值"The Window"。
每個函數在被調用時,其使用中的物件都會自動取得兩個特殊變數: this和arguments。內建函式在搜尋這兩個變數時,只會搜尋到其使用中的物件為止,因此永遠不可能直接存取外部函數中的這兩個變數。 不過,把外部範圍中的this對象儲存在一個閉包能夠訪問到的變數裡,就可以讓閉包訪問該對象了。
var name = "The Window";var object = { name: "My Object", getNameFunc: function(){ var that = this; return function(){ return that.name; }; }};alert(object.getNameFunc()()); //My Object
在定義匿名函數之前,把this對象賦值給了一個名叫that的變數。而在定義了閉包之後,閉包也可以訪問這個變數。即使在函數返回後,that也仍然引用著object,所以調用object.getNameFunc()()就返回了"My Object"。
JS函數運算式 -- 閉包