JavaScript之閉包


前言:最近在細讀Javascript高級程序設計,對於我而言,中文版,書中不少地方翻譯的差強人意,因此用本身所理解的,嘗試解讀下。若有紕漏或錯誤,會很是感謝您的指出。文中絕大部份內容引用自《JavaScript高級程序設計第三版》。前端


閉包是指有權訪問另外一個做用域中的變量的函數。閉包

建立閉包的常見方式, 就是在一個函數內部建立另一個函數。函數

function createComparisonFunction(propertyName) {

    return function(object1, object2) {

        var value1 = object1[propertyName]; 
        // 注意, return出的匿名函數訪問了外部函數中的變量propertyname
        var value2 = object2[propertyName];
        // 注意, return出的匿名函數訪問了外部函數中的變量propertyname

        if(value1 < value2) {
            return -1;
        } else if (value > value2) {
            return 1;
        } else {
            return 0;
        }

    }
}

在這個例子中,突出的那兩行代碼是內部函數(一個匿名函數)中的代碼,這兩行代碼訪問了外部函數中的變量propertyName。優化

即便這個內部函數被返回了,並且是在其餘地方被調用了,但它仍然能夠訪問變量propertyName。翻譯

之因此還可以訪問這個變量,是由於內部函數的做用域鏈中包含createComparisonFunction()的做用域。設計

當某個函數被調用時,會建立有一個執行環境(execution context)以及相應的做用域鏈。
而後,使用arguments和其餘命名參數的值來初始化函數的活動對象(activation object)。指針

但在做用域鏈中,外部函數的活動對象始終處於第二位,外部函數的外部函數的活動對象始終處於第三位,...直至做爲做用域鏈重點的全局執行環境。code

在函數執行過程當中,爲讀取和寫入變量的值,就須要在做用鏈中查找變量。對象

function compare(value1, value2) {
    if(value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5,10);

以上代碼先定義了compare()函數,而後又在全局做用中調用了它。ip

當調用compare()時,會建立一個包含arguments、value1和value2的活動對象。

全局執行環境的變量對象(包含result和compare)在compare()執行環境的做用鏈中處於第二位。

function compare(value1, value2) {
    if(value1 < value2) {
        return -1;
    } else if (value1 > value2) {
        return 1;
    } else {
        return 0;
    }
}

var result = compare(5,10);

//做用域鏈僞代碼
/*
compare[[scope]] = {
    global variable: {
        result, compare
    }, 
    local variable: {
        arguments: [5, 10],
        value1 : 5,
        value2: 10
    }
}
*/

後臺的每一個執行環境都有一個表示變量的對象——變量對象。
全局環境的變量對象始終存在,而compare()函數這樣的局部環境的變量對象,則只在函數執行的過程當中存在。

在建立compare()函數時,會建立一個預先包含全局變量對象的做用域鏈,這個做用域鏈被保存在內部的[[Scope]]屬性中。

當調用compare()函數時,會爲函數建立一個執行環境,而後經過複製函數的[[Scope]]屬性中的對象構建起執行環境的做用域鏈。此後,本地活動對象(也能夠說是函數執行時,內部的變量)被建立並被推入執行環境做用域的前端。

對於函數的執行環境而言,其做用域鏈中包含兩個變量對象:

1. 本地活動對象。

2. 全局變量對象。

做用域鏈本質上是一個指向變量對象的指針列表,它只引用但不包含變量對象。

不管何時在函數中訪問一個變量時,就會從做用域鏈中搜索具備相應名字的變量。通常來說,當函數執行完畢後,局部活動對象就會被銷燬,內存中僅保存全局做用域(全局執行環境的變量對象)。

可是,閉包的狀況又有所不一樣。

在另外一個函數內部定義的函數會將包含函數(即外部函數)的活動對象添加到它的做用鏈中。
所以, 在createComparisonFunction()函數內部定義的匿名函數的做用域鏈中,實際上將會包含外部函數createComparisonFunction()的活動對象。

function createComparisonFunction(propertyName) {

    return function(object1, object2) {

        var value1 = object1[propertyName];
        var value2 = object2[propertyName];

        if(value1 < value2) {
            return -1;
        } else if(value1 > value2) {
            return 1;
        } else {
            return 0;
        }
    }

}

// 注意調用createComparisonFunction的返回值仍然是一個函數。
var compare = createComparisonFunction("name");
/*
createComparisonFunction[[Scope]] = {
    global Variable: {
        createComparisionFunction
    },
    local Variable: {
        arguments: ["name"],
        propertyName: "name"
    }
}

*/

var result = compare({name: "Nicholas"}, {name:"Greg"});

/*
compare[[Scope]] = {
    global Variable: {
        createComparisonFunction
    },
    local Variable: {
        arguments: [{name: "Nicholas"}, {name:"Greg"}],
        value1: arguments[0][propertyName],
        value2: arguments[1][propertyName]
    }
}

//propertyName仍然引用createComparisonFunction中的propertyName
*/

createComparisonFuncion()函數在執行完畢後,其活動對象也不會被銷燬,由於匿名函數的做用域鏈仍然在引用這個活動對象。

換句話說, 當createComparisonFunction()函數返回後, 其執行環境的做用域鏈會被銷燬,但它的活動對象仍然會留在內存中;直到匿名函數被銷燬後,createComparisonFunction()的活動對象想象纔會銷燬。

//建立函數
var compareNames = createComparisonFunction("name");

//調用函數
var result = compareNames({name: "Nicolas"}, { name: "Greg"});

//接觸對匿名函數的引用(以便釋放內存),固然若是你就是須要使用該變量,那麼就不用釋放了,也要看狀況的。
compareNames = null;

因爲閉包會攜帶包含它的函數的做用域,所以會比其餘函數佔用更多的內存。過分使用閉包可能會致使內存佔用過多,所以建議只在絕對必要時再考慮使用閉包。雖然像V8等優化後的JavaScript引擎會嘗試回收被閉包占用的內存,可是仍是要慎重的使用閉包。

相關文章
相關標籤/搜索