JavaScript之闭包的实现、闭包中的this对象

闭包

函数对象可以通过作用域链关联起来,函数体内的变量可以保存在作用域中,这种特性称“闭包”。

要理解闭包,首先要理解嵌套函数的词法作用域规则:先看下列一段代码:

var a = "Tom"; //全局变量

function curr () {
    var a = "Bob";  //局部变量
    function () {
        return a;
    }
    return f(); //将f函数执行的结果返回 Bob
}

curr();


当curr()函数调用执行完毕后,返回的是f()函数执行的结果即”Bob“, 当将这段代码修改之后

var a = "Tom";
function curr () {
    var a = "Bob";
    function f () {
        return a;
    }
    return f; //返回的是这个f函数对象
}

curr()(); //f()


当curr()函数执行完毕后,返回的是f函数对象,后面再跟一个圆括号,就是调用f函数。定义curr函数时,创建了一个作用域链,调用curr函数时,会新创建一个对象用来保存这个函数的局部变量和参数,并把这个对象保存在这个作用域上,函数f也定义在了这个作用域链中。当curr()函数执行后,其作用域链依然有效,上面还保存着其局部变量a,嵌套函数f也还在了这个作用域链上,因此此时局部变量a和f函数还是绑定在一起的。最后执行f函数的结果是"Bob"(引用的变量a就是这个局部变量)。


词法作用域规则:JavaScript函数执行的时候用到了作用域链,这个作用域链在定义函数的时候创建的,嵌套的f函数定义在这个作用域链中,其中变量a是局部变量,无论何时何地执行f函数,这种绑定在执行f时依然有效。



实现闭包:

要理解闭包,首先理解词法作用域链规则函数定义时所创建的作用域链到函数执行时依然有效

首先回顾一下作用域链:定义函数时就会创建作用域链,将作用域链看作是对象列表或链列表。每次调用函数时,就会创建一个新对象用来保存(定义)局部变量,并把这个对象添加到作用域链中。当函数返回时,就将这个对象从作用域链中删除:

1、如果这个函数没有定义嵌套的函数,也没有其它引用指向这个对象,那么这个对象就会被回收掉。

2、如果这个函数定义了嵌套的函数,每个嵌套的函数都有自己的作用域链,并且这个作用域链指向一个变量绑定对象:
  • a、如果这些嵌套的函数在函数外部保留了下来,那么它们也会和所指向的变量绑定对象被当作垃圾回收掉。
  • b、如果 这个嵌套的函数对象 被当作返回值返回或作为某对象的属性保存了下来,这时就会有一个外部引用指向这个嵌套的函数对象,它就不会被当作垃圾回收掉,并且它所指向的变量绑定对象也不会被回收掉。

下面举个经典例子:返回一个函数组成的数组,它们返回0~9的数
functino consFunc () {
    var arr = [];
    for (var i = 0; i < 10; i ++) {
        arr[i] = function () {
            return i;
        };
    }
    return arr;
}

var a = consFunc(); //调用并返回consFunc
console.log(a[5]()); //10 看来没有返回我们想要的值


上面代码创建了10个闭包,并将它们储存在一个数组中,因为每个闭包都是在同一个函数 调用中 定义的,因此它们都引用同一个局部变量i。当consFunc返回时,变量i的值为10,再使用a[5]()调用闭包函数,此时所有闭包函数都引用同一个变量i,但此时的变量i的却是10,所以数组中的函数的返回值是同一个值。这不是我们想要的。嵌套的函数不会将作用域中的私有变量复制一份的。


解决方案1:可以创建一个匿名函数让闭包来”记住“变量i的不同值。

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

var a = consFunc();
console.log(a[5]()); //5

在重写了consFunc函数后,每个函数都会返回各自不同的索引值了。这里定义了一个匿名函数,并将立即执行这个匿名的结果赋值给数组。这里的匿名函数有个参数num,也就是最终要返回的值。在调用每个匿名函数(闭包)时,传入了变量i。由于函数是按值传递的,所以就会将变量i的当前值复制给num,在这个闭包函数内部,又创建一个闭包函数作为返回值,于是数组中存放的是这个返回值,这个返回值是一个函数对象,arr数组中的每个函数都有自己的num变量的副本,因此可以返回不同的值。


解决方案2:其实可以将上面代码修改为更简洁的方式:

function consFun () {
    var arr = [];
    for (var i = 0; i < 10; i ++) {
        arr[i] = function () { //调用匿名函数自身,返回的是值。
            return i; 
        }();
    }
    return arr;
}

var a = consFunc();
console.log(a[5]); //5

当consFunc函数返回时,数组中的闭包函数已经执行完毕,并将执行的结果保存在了数组中。



在闭包中关于this对象


我们知道,this对象是在运行时基于函数的执行环境绑定的:在全局环境中,this对象就是window对象。而当函数作为某个对象的方法调用时,this对象就指向这个对象。匿名函数具有全局性,this对象通常指向window对象,不过有时候,由于闭包函数书写方式不同,就没有那么明显。

特别说明:当函数被调用时会自动取得两个属性:this对象和arguments对象。


var name = "hello window";

var obj = {
    name : "hello obj",


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

console.log(obj.getName()()); //hello window


上例定义了一个全局变量name和一个对象obj。当obj.getName()方法,返回的是一个匿名函数对象,而匿名函数又返回"this.name",由于getName返回的是一个函数,因此"obj.getName()()"就会立即调用getName返回的匿名函数,此时匿名函数是作为全局对象window的方法调用的,因此匿名函数中的this对象指向window对象,以"this.name"就相当于"window.name",所以最后返回的结果是"hello window"。

上述例子在开始创建前,想要的是匿名函数返回obj对象中的name属性。但匿名函数中的this对象是指向window对象的,无法直接引用到obj对象。我们知道在"obj.getName()"中,getName函数是作为obj对象的方法被调用的,那么其this对象就指向了obj对象,那我们可不可以这样想:如果能使匿名函数(内部函数)引用到getName函数的this对象的话,那么间接地不就引用到了obj对象了么,匿名函数最后返回的不就是想要的"hello obj"么。


我们知道,函数在被调用时会自动添加两个属性:this对象和arguments对象。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此不可能直接访问到外部函数的这两个变量。如果将外部的this对象保存在一个闭包函数(内部函数)可以访问到的变量里,是不是可以说这个闭包函数就可以访问到外部函数的this对象呢?

var name = "hello window";

var obj = {
    name : "hello obj",


    getName : function () {


        var that = this; //将getName函数的this对象保存在一个变量里。
        return function () {
            return that.name; //由闭包函数去访问这个变量。
        }
    }
};

console.log(obj.getName()()); //hello obj


这样,将getName函数的this对象保存在一个变量里,而这个变量可被闭包函数访问,当闭包函数搜索that.name时,就会搜索到getName函数的this.name,getName函数的this对象是引用到obj对象的(that就相当于此this),即使这个函数返回后,that也是引用到obj对象的,那么最后闭包函数返回的就是"hello obj"。



注意:闭包函数中的that引用的是obj对象,但其this对象引用的是window对象。

var name = "hello window";

var obj = {
    name : "hello obj",


    getName : function () {


        var that = this; //将getName函数的this对象保存在一个变量里。
        return function () {
            return that.name + "-" + this.name; // 此处的this对象是指向window对象的。
        }
    }
};

console.log(obj.getName()()); //hello obj - hello window

也就是说,尽管这样修改代码,这个闭包函数还是作为window对象的方法调用的。


你可能感兴趣的:(Javascript学习笔记)