JavaScript之闭包

一、闭包的定义

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。——《JavaScript权威指南》

二、为什么使用闭包

首先让我们来看一下全局变量与局部变量的区别:
全局变量:可以反复使用,且共享使用;但是可能随时在任意位置被篡改(全局污染)。
局部变量:不可反复使用;但方法调用完毕自动释放。
所以我们想要一种即不会被污染,又可以反复使用的局部变量时,就可以使用闭包。

三、如何使用闭包

使用闭包需要三步:

1.定义外层函数
特点:定义了受保护的局部变量,返回了一个专门操作局部变量的内部函数对象。
2.调用外层函数,获得返回的内部函数对象。
3.使用获得的内部函数对象,操作受保护的局部变量。(这是操作受保护局部变量的唯一途径

只看定义不能让人很好的理解,下面看一下示例代码:

//定义外层函数保护局部变量
function fun(){
    var n=0;
    return function(){
        return ++n;
    }
}
//获得返回的内部函数对象
var f1=fun();//实际上,var f1=function(){return ++n;}
//使用内部函数对象操作受保护的变量
console.log(f1());//1
console.log(f1());//2
n=1;//在此处尝试修改受保护的变量,并没有影响输出的结果
console.log(f1());//3

四、如何判断闭包

1.必须有内外层函数嵌套;
2.内层函数必须使用外层函数的局部变量;
3.外层函数将内层函数返回到外部,可在外部调用。
注:可以参考上面的示例代码,对应判断的条件。

五、判断闭包的结果

1.外层函数调用了几次,就有几个受保护的局部变量副本;
2.同一次外层函数调用返回的内部函数对象,操作同一个变量。

上面的例子是一个最简单的闭包,下面来看一些有点意思的:

function outer(){
        for(var i=0,arr=[];i<3;i++){//i受保护的变量
            arr[i]=function(){return i};
        }//i变成了3
        return arr;
    }
    var funs=outer(); //外层函数调用一次,只有1个i
    console.log(funs[0]()); //3
    console.log(funs[1]()); //3
    console.log(funs[2]()); //3

从表面上看,似乎每个函数都应该返回自己的索引,即0位置的函数返回0,1位置的函数返回1,然而实际并不是这样;实际上,每个函数都返回3。

注:作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取到包含函数中任何变量的最后一个值。——《JavaScript高级程序设计》

根据判断闭包结果的条件,上述代码只调用了一次外层函数outer(),所以只有一个受保护的变量,因为每个函数的作用域链中都保存着outer()函数的活动对象,所有它们引用的都是同一个变量i;当outer()函数返回后,变量i的值是3,此时每个函数都引用着保存变量i的同一个变量对象,所以每个函数内部i的值都是3。

当然如果我们要实现每个函数都返回自己的索引也是有办法的,看下面的代码:

function outer(){
        for(var i=0,arr=[];i<3;i++){//i受保护的变量
            arr[i]=function(num){
                   return function(){
                       return num;
                   }
            }(i);
        }//i变成了3
        return arr;
    }
    var funs=outer(); 
    console.log(funs[0]()); //0
    console.log(funs[1]()); //1
    console.log(funs[2]()); //2

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

六、闭包中的this对象

看下边两个代码:

//例6-1
var name="global";
var obj={
    name:"local",
    getNameFun:function(){
        return function(){
            return this.name;   
        };
    }
};
console.log(obj.getNameFun()());//"global"

//例6-2
var name="global";
var obj={
    name:"local",
    getNameFun:function(){
        var self=this;
        return function(){
            return self.name;   
        };
    }
};
console.log(obj.getNameFun()());//"local"

内部函数在搜索this变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。当然这并不是没有解决办法,如例6-2中,把外部作用域中的this对象保存在一个闭包能够访问的变量里,就可以让闭包访问该对象了。

个人觉得下面的代码是比较难的(有兴趣的可以自己研究一下):

var o=10;
var foo={
    o:9,
    bar:function(){
      return this.o;
    }
}
console.log(foo.bar());//9   
var bar=foo.bar;
console.log(bar());//10 
console.log((foo.bar=foo.bar)());//10
console.log((foo.bar,foo.bar)());//10

七、闭包的问题

1.由于闭包在内存中造成了循环引用,而当函数的执行环境被释放时,函数的活动对象还被引用无法释放,所以比普通函数占用更多的内存空间;
2.当DOM对象和JavaScript对象之间存在循环引用时需要格外小心,在某些浏览器(主要是IE)下会造成内存泄漏。

简单来说就是,如果闭包的作用域链中保存着一个HTML元素,那么久意味着该元素将无法被销毁。

以下均出之《JavaScript高级程序设计》:

//例7-1
function assignHandler(){
    var element=document.getElementById("someElement");
    element.onclick=function(){
        alert(element.id);
    }
}

以上代码创建了一个作为element元素时间处理程序的闭包,而这个闭包则又创建了一个循环引用。由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就导致无法减少element的引用数。只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被收回。不过可以稍微改写一下代码来解决,如例7-2

//例7-2
function assignHandler(){
    var element=document.getElementById("someElement");
    var id=element.id;
    element.onclick=function(){
        alert(id);
    }
    element=null;
}

注:仅仅做到这一步,还是不能解决内存泄漏的问题。
必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把element变量设置有null。这样就能够解除对DOM对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值