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引擎会尝试回收被闭包占用的内存,可是仍是要慎重的使用闭包。

相关文章
相关标签/搜索