JavaScript闭包的总结

闭包

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

function compareFunction(propertyName) {  
    return function(object1,object2) {
        var value1 = object1[propertyName];
        var value2 = objectp[propertyName];

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

    }
}

通过内部函数(一个匿名函数)访问了外部函数中的变量propertyName,即使这个内部函数返回了,在其他地方被调用,它仍然可以访问变量propertyName。因为内部函数的作用域链中包含compareFUnction()的作用域

作用链

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

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

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 的活动对象。全局执行环境的变量对象(包含 result 和 compare )在 compare() 执行环境的作用域链中则处于第二位。 图 7-1 展示了包含上述关系的 compare()函数执行时的作用域链。

后台的每个执行环境都有一个表示变量的对象——变量对象。全局环境的变量对象始终存在,而像 compare()函数这样的局部环境的变量对象,则只在函数执行的过程中存在。

在创建 compare()函数 时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。 当调用 compare()函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对 象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执 行环境作用域链的前端.

显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。

无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量。一般来讲, 当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。

闭包的情况有所不同。

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

var compare = createComparisonFunction("name");  
var result = compare({ name: "My Name" }, { name: "Xu" });  

匿名函数从compareFunction()中被返回后,它的作用域链被初始化为包含compareFunction()函数的活动对象和全局变量对象。这样,匿名函数就可以访问在compareFunction()中定义的所有变量。

compareFunction()函数在执行完毕后,其活动对象不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。

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

var compareNames = compareFunction("Reddy");  
var result = compareNames({name: "Xu"},{name: "Lei"});  
compareNames = null;  

闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最 后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。

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

这个函数会返回一个函数数组。表面上看,似乎每个函数都应该返自己的索引值,即位置 0 的函数 返回 0,位置 1 的函数返回 1,以此类推。但实际上,每个函数都返回 10。因为每个函数的作用域链中 都 保 存 着 createArray() 函 数 的 活 动 对 象 , 所 以 它 们 引 用 的 都 是 同 一 个 变 量 i 。 当 createArray()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量 对象,所以在每个函数内部 i 的值都是 10。但是,我们可以通过创建另一个匿名函数强制让闭包的行为 符合预期

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

我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋 给数组。这里的匿名函数有一个参数 num,也就是最终的函数要返回的值。在调用每个匿名函数时,我 们传入了变量 i。由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num。而在这个 匿名函数内部,又创建并返回了一个访问 num 的闭包。这样一来,result 数组中的每个函数都有自己 num 变量的一个副本,因此就可以返回各自不同的数值了。

闭包和this

在闭包中使用 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()()就会立即调用它返回的函数,结果就是返回一个 字符串。然而,这个例子返回的字符串是"The Window",即全局 name 变量的值

每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函 数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变 量(这一点通过图 7-2 可以看得更清楚)。不过,把外部作用域中的 this 对象保存在一个闭包能够访问 到的变量里,就可以让闭包访问该对象了

var name = "The Window";

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

};

alert(object.getNameFunc()());//"My Object"  

Partial Application

Partial Application是一个接受一个多个参数函数二返回更少参数函数的函数,它以一个任意数量参数的函数和我们想要应用在函数上的实参,并且返回一个接着剩余实参的函数

const add = (a, b) => a + b;  
const add10 = partialApply(add, 10);//a function that adds 10 to any number.  
add10(5);//15  
const partialApply = (fn, ...fixedArgs) => {  
  return function (...remainingArgs) {
    return fn.apply(this, fixedArgs.concat(remainingArgs));
  };
};