JS函數運算式 -- 閉包

來源:互聯網
上載者:User

標籤:

閉包是指有權訪問另一個函數範圍中變數的函數。 建立閉包的常見方式,就是在一個函數內部建立另一個函數。本質上講,閉包就是講函數內部和函數外部串連起來的一座橋樑。

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函數運算式 -- 閉包

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.