初始 JavaScript - 闭包

1,965 阅读3分钟

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。

function createComparisonFunction(property) {
    return funtcion (f1,f2) {
        return f1(property) == f2(property);
    }
}

f1()f2()都能访问到createComparisonFunction)函数的property属性,是因为f1()f2()函数的作用域中包含createComparisonFunction()的作用域。

当某个函数被调用时,会创建一个执行环境(execution context)及相应的作用域链。然后,使用 arguments 和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。

下图为下面代码实现时的函数的作用链:

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

从上图可以看出:

  • 全局变量对象是一直存在的,而局部变量只在函数执行过程中存在。
  • 函数的作用链中,函数本身的arguments在第一位,外部createComparisonFunction函数的arguments排在第二位,而全局变量对象处在第三位。
//获得其匿名函数
var compare = createComparisonFunction("name");
var result = compare({ name: "Nicholas" }, { name: "Greg" });

在匿名函数从 createComparisonFunction()中被返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动对象和全局变量对象。

createComparisonFunction()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中;直到匿名函数被销毁后, createComparisonFunction()的活动对象才会被销毁,

所以在清除闭包函数时,不仅需要将函数清除,也需要清除函数内的闭包。

闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。

如下面例子:

function createFunctions(){
    var result = new Array();
    for (var i=0; i < 4; i++){
        result[i] = function(){
            return i;
    };
}
return result;//返回的结果result[0]=result[1]=result[2]=result[3]=4;
}

上面例子的作用链图如下:

可以看出i是createFunctions函数的活动对象,所以内部函数引用的都是同一个i,当createFunctions返回后,i为4,所以数组中值都为4。

解决上面问题方法就是创建另一个匿名函数。

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

定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。这样函数当时执行的i值,会立即传给num。在这个匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来, result 数组中的每个函数都有自己num 变量的一个副本,因此就可以返回各自不同的数值了。

关于this对象

this 对象是在运行时基于函数的执行环境绑定的:在全局函数中, this 等于 window,而当函数被作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

function func1() {
    alert(this);
}
func1();//window

直接定义函数func1,相当于在全局环境下定义,所以this指向window。

var o = {
    name: "object",
    func2:function() {
        alert(this);    
    }
};
o.func2();//object

此时,函数是通过对象o调用,所以this指向object。

var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

object.getNameFunc()返回的是匿名函数,此时不是通过getNameFunc()函数调用的,所以this指向window。

var name = "The Window";
var object = {
    name : "object",
    getNameFunc : function(){
        var that =  this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()()); //"My Object"

getNameFunc()中this指向的My Object,所有先赋值给that,然后匿名函数返回时,that的值仍然保持,所以最后返回的是My Object