[JS]理解閉包

執行環境和作用域

每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行之後,棧將其環境彈出,把控制權返還給之前的執行環境。
當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈,保證對執行環境有權訪問的所有變量和函數的有序訪問。作用域的前端,始終都是當前執行的代碼所在環境的變量對象。如果這個環境是函數,則將其活動對象作爲變量對象。活動對象在最開始時只包含一個變量,即arguments對象(這個對象在全局環境中是不存在的)。作用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是作用域鏈中的最後一個對象。[1]

var color0="blue";

function ChangeOnce(){
    var color1 = "yellow";

    function ChangeTwice(){
        var color2 = "red";
        color1 = color0;
        color0 = color2;
        //這裏能訪問color0\color1\color2
    }
    //這裏能訪問color0\color1
}
//這裏只能訪問color0

以上共涉及三個執行環境:全局環境、 changeOnce() 的全局環境和 changeTwice() 的全局環境。內部環境可以通過作用域鏈訪問所有外部環境,但外部環境不能訪問內部的任何變量和函數。

函數內部聲明變量時,一定要使用 var 命令,否則,該變量是一個全局變量!

延長作用域鏈

雖然執行環境的類型總共只有兩種:全局和局部,但可以通過其他辦法來延長作用域鏈。當執行流進入下列任一語句時:作用域鏈就會延長。

  • try-catch語句的catch塊;
  • with語句
function Name(){
    var firstName = "Smith";

    with(firstName){
        var name = firstName + " " +" Whale"
    }
    return name;
}

閉包

當外部需要訪問內部函數或者變量時該怎麼辦呢?這是就需要用到 閉包

閉包就是能夠讀取其他函數內部變量的函數。由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成”定義在一個函數內部的函數”。
所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。——阮一峯的博客

閉包最大的用途有兩個:
- 讀取函數內部的變量
- 讓變量值始終保持在內存中

function f1(){
    var n = 999;
    nAdd = function(){ n+=1}
    function f2(){
        alert (n);
    }
    return f2;
}
var result = f1();
result();    //999
nAdd();
result();    //1000

在上面的代碼中, 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,可以在函數外部對函數內部的局部變量進行操作。——阮一峯的博客

閉包與變量

閉包只能取得包含函數中任何變量的最後一個值。如:

function createFunciton(){
    var result = new Array();

    for(var i=0; i < 10; i++>){
        result[i] = funciton (){
            return i;
        }
    }
    return result;
}

這個函數返回一個函數數組。表面上看,似乎每個函數應該返回自己的索引值,即位置0的函數返回0,位置1的函數返回1,以此類推。但實際上,每個函數都返回10。因爲每個函數的作用域中都保存着 creationFunction() 函數的活動對象,所以它們引用的都是同一個變量 i 。當 creationFunction() 函數返回後,變量 i 的值是10,所以在每個函數內部i的值都是10.但是我們可以通過創建另一個匿名函數強制讓閉包的行爲符合預期。如下:

function createFunciton(){
    var result = new Array();

    for(var i=0; i < 10; i++>){
        result[i] = funciton (num){
            return function(){
                return num;
            };
        }(i);
    }
    return result;
}

在這個函數中,定義了一個匿名函數,並立即執行匿名函數的結果賦給數組。這裏的匿名函數有一個參數 num ,也就是最終的函數要返回的值。在調用匿名函數時,傳入的變量 i 的當前值按值傳遞賦值給參數 num 。在這個匿名函數內部,又創建並返回了一個訪問 num 的閉包。這樣一來, result 數組中的每一個函數都有了自己的 num 變量的一個副本,因此可以返回各自不同的數值。

[1]:《Javascript高級程序設計》(第3版) [美]Nicholas C.Zakas著 李鬆峯 曹力譯

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章