第七章 函数表达式(一)

2018-05-11  本文已影响12人  伊凡的一天

前面提到过,定义函数的方式有两种:函数声明函数表达式。下面分别是使用函数声明和函数表达式定义函数的例子:

//函数声明
function functionName(arg0, arg1,arg2){
}

//函数表达式
var functionName = function(arg0, arg1, arg2){
};

函数声明定义的函数具有提升(不在乎定义和调用的前后顺序)的效果,而函数表达式则没有。这是两者唯一的区别。

1. 闭包

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

function createComparisionFunction(propertyName){
    return funtion(obj1, obj2){
        var v1 = obj1[propertyName];
        var v2 = obj2[propertyName];
        if (v1 < v2){
            return -1;
       } else if (v1 > v2){
            return 1;
       }else{
            return 0;
       }
    };
}

在这个例子中,内部函数中var v1 = obj1[propertyName];的这行语句访问了外部函数中的propertyName变量。即使这个内部函数返回后,被其他函数调用,它依旧能够访问这个propertyName变量。这是因为这个内部函数的作用域链上包含了createComparisionFunction()函数的作用域。

ECMAScript中每个执行环境(Execution Context)都包含了一个保存变量的对象——变量对象(Variable Object)。全局环境的变量对象始终存在,而局部环境的变量对象,则只有在函数运行时才会被创建。在创建createComparisionFunction()函数时,会先创建一个预先包含全局变量对象的作用域链,这个作用域链保存在函数的内部属性[[Scope]]中。当真正调用函数时,首先会为函数创建一个执行环境,然后通过复制函数[[Scope]]属性中保存的对象构建出一条作用域链。此后,创建一个活动对象(Activation Object,作为变量对象使用)并将此活动对象推入执行环境作用域链的前端。显然,作用域链的本质是一个指向变量对象的指针列表,它只包含一系列指向变量对象的引用。下图清晰的解释了createComparisionFunction()函数的作用域链:


image.png

在函数中访问一个变量时,会从该函数的作用域链上搜索具有相应名字的变量。一般来说,当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)。但是,闭包的情况又有所不同。

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

当匿名函数从createComparisonFunction()函数中返回后,它的作用域链被初始化为包含createComparisonFunction()函数的活动变量和全局变量对象。这样,匿名函数就可以访问createComparisonFunction()函数中定义的所有变量。更为特殊的是,createComparisonFunction()函数执行完毕后,其执行环境的作用域链会被销毁,然而其活动对象却仍然存活,因为匿名函数的作用域链依旧引用着其活动对象。直到匿名函数被销毁后,createComparisonFunction()函数的活动对象才会被回收。

1.1 闭包与变量

作用域链的机制引申出一个值得注意的副作用,即闭包只能去得包含函数中变量的最后一个值。因为闭包访问的是整个变量,而不是变量的某个特殊值。下面是一个例子:

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

上面的例子返回一个函数数组,表面上看,每个函数似乎都会返回自己的索引值。但实际上,每个函数返回的都是10。这是因为每一个匿名函数都保存着createFunctions()函数的活动对象,所以它们引用着同一个变量i。当createFunctions()函数返回后,变量i的值是10,因此每个匿名函数返回的变量i的值都是10。但是,我们可以通过创建另一个匿名函数强行让闭包的行为符合预期:

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

猜你喜欢

热点阅读