JS函数表达式 -- 闭包

闭包是指有权访问另一个函数作用域中变量的函数。 创建闭包的常见方式,就是在一个函数内部创建另一个函数。本质上讲,闭包就是讲函数内部和函数外部连接起来的一座桥梁。

function a(){
    var i = 0;
    function b(){
        alert(++i);
    }
    return b;
}
var c = a();
c(); //1

在函数a 中嵌套了函数b,并将函数b返回。

在执行完 var c = a() 后,变量c实际上指向了函数b,再执行c()后就会弹出一个窗口显示i的值。

当函数a的内部函数b被函数a外的一个变量引用时,就创建了一个闭包。

闭包主要涉及到js的几个其他的特性: 作用域链,垃圾(内存)回收机制,函数嵌套

function compare(value1, value2){
    if(value1 > value2){
        return 1;
    }else if(value1 < value2){
        return -1;
    }else{
        return 0;
    }
}
var result = compare(5,10);

1. 作用域链:

  作用域链是在函数定义时创建的,当函数需要查询一个变量的值的时候,js解析器会去作用域链中查找。在上述例子中,查找i时,会先在函数b中找,找到则返回,没找到继续在上一级的链即函数a中查找,如果还没找到,再到全局链中找,如果全局链中也没有,则返回undefined。

  在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。  当调用函数时,会为函数创建一个执行环境,然后通过复制函数[[Scope]]属性中的对象构建起执行环境的作用域链。

  在上面的例子中,对于compare()函数的执行环境而言,其作用域中包含两个变量对象: 本地活动对象和全局变量对象。作用域链本质上是一个指向变量对象的指针表,它只引用但不实际包含变量对象。

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

2.垃圾回收机制:

一个函数在执行开始的时候,会给其中定义的变量划分内存空间,用来保存这些变量。等函数执行完毕后,这些变量的内存就会被回收。下次再执行时,又会重新分配。

如果一个函数的内部嵌套了另外的一个函数,内部函数有可能在外部被调用到,并且这个内部函数使用了外部函数的某些变量。

这种情况下,js解释器在遇到函数定义的时候,会自动把函数和它可能用到的变量(包括本地变量、父级和祖父级函数的变量)一起保存下来,也就是构建一个闭包。

这些变量将不会被内存回收器回收。只有当内部的函数不可能被调用以后,才会销毁这个闭包。

3.闭包与变量:

  闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量。

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

createFunctions();
alert(result[0]());    //10
alert(result[9]());    //10

这个函数会返回一个函数数组,表面上看,似乎每个函数都应返回自己的索引值,但实际中,每个函数都返回10。

因为每个函数的作用域链中都保存着createFunctions()函数的活动对象,所以他们引用的都是同一个变量 i。当createFunctions()函数返回后,变量i的值时10,

此时,每个函数都引用着保存变量i的同一个变量对象,所以在每个函数内部 i 的值都是10。

我们可以创建另一个匿名函数强制让闭包的行为符合预期。

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

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

4.关于this对象:

  在闭包中使用this对象也会导致一些问题。this对象是在运行时基于函数的执行环境绑定的: 在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于当前对象。 不过,匿名函数的执行环境具有全局性,因此其this对象通常指向window。但有时候,由于编写闭包的方式不同,这一点可能不会那么明显:

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

以上代码先创建了一个全局变量name,又创建了一个包含name属性的对象。这个对象还包含一个方法 -- getNameFunc(), 它返回一个匿名函数,而匿名函数又返回this.name。由于getNameFunc()返回一个函数,因此调用object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串。然而这个结果是全局name变量的值"The Window"。

  每个函数在被调用时,其活动对象都会自动取得两个特殊变量: 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()());    //My Object

在定义匿名函数之前,把this对象赋值给了一个名叫that的变量。而在定义了闭包之后,闭包也可以访问这个变量。即使在函数返回后,that也仍然引用着object,所以调用object.getNameFunc()()就返回了"My Object"。

 

posted @ 2016-08-19 16:05  chenccc  阅读(623)  评论(2编辑  收藏  举报