JavaScript:闭包

闭包

闭包:指有权访问另一个函数作用域中的变量的函数。

创建闭包的常见方式就是在一个函数内部创建另一个函数:

function createComparisonFunction(propertyName) {
    return function (obj1, obj2) {
        // 访问了外部函数中的变量
        var value1 = obj1[propertyName];
        var value2 = obj2[propertyName];

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

上面的obj1、obj2访问了外部函数的变量propertyName。之所以能访问到这个变量,是因为内部函数的作用域中包含createComparisonFunction()的作用域。

当某个函数被调用时,会创建一个执行环境及相应的作用域链。然后,使用arguments和其他命名参数的值来初始化函数的活动对象。

在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。

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

var result = compare(5, 10);

上面先调用了compare()函数,然后又在全局作用域调用了它,当调用compare()时,会创建一个包含arguments、value1和value2的活动对象。全局执行环境的变量对象在compare()执行环境的作用域链中则处于第二位。

compare()函数执行时的作用域链:

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。因此createComparisonFunction()函数内部定义的匿名内部函数的作用域链中,实际上将会包含外部函数createComparisonFunction()的活动对象。

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

下图展示了代码执行时,包含函数与内部匿名函数的作用域链。

匿名函数从createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在createComparisonFunction()中定义的所有变量。另外,在createComparisonFunction()执行完,它的活动对象不会被销毁,从图中可以看出,匿名函数还引用了这个活动对象。当createComparisonFunction()函数返回时,其执行环境的作用域链会被销毁,但它的活动对象仍会留在内存中;匿名函数被销毁后,createComparisonFunction()的活动对象才会销毁。

// 创建函数
var compareNames = createComparisonFunction("name");

// 调用函数
var result = compareNames({name: "Nicholas"}, {name: "Greg"});

// 解除对匿名函数的引用(释放内存)
compareNames = null;

首先,创建的比较函数被保存在compareNames,将compareNames设置为null,解除了该函数的引用,就可以进行清除了。

闭包与变量

闭包保存了整个变量对象,但只能取得包含函数中任何变量的最后一个值。

示例:

function createFunctions() {
    var result = new Array();
    
    for (var i = 0; i < 10; i++) {
        result[i] = function () {
            return i;
        };
    }
    return result;
}

上面的例子中看似每个函数都返回自己的索引值,实际上,每个函数都返回10。因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以它们引用的都是同一个变量i。

通过创建另一个匿名函数强制让闭包的行为符合预期,如下:

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

this对象

this对象在运行时是基于函数的执行环境进行绑定的,在全局函数中,this等于window;当某个对象的方法调用时,this等于那个对象。但是,匿名函数的执行环境具有全局性,其this对象指向window。

看一个例子:

var name = "The Window";

var object = {
    name: "My Object",

    getNameFunc: function () {
        return function () {
            return this.name;
        };
    }
};

alert(object.getNameFunc()());  // "The Window"

上面的例子中首先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象包含一个方法:getNameFunc(),它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会调用它返回的函数,结果为:this.name。然而,返回的字符串是"The Window"。

为什么匿名函数没有取得其包含作用域的this对象呢?

每个函数在函数在被调用时都会获取两个变量: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()());  // "The Window"
posted @ 2018-08-18 21:27  抱臂柴  阅读(145)  评论(0编辑  收藏  举报