JavaScript之闭包

来源:互联网 发布:人工智能芯片股票龙头 编辑:程序博客网 时间:2024/05/21 10:12

一、闭包的定义

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

二、为什么使用闭包

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

三、如何使用闭包

使用闭包需要三步:

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

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

//定义外层函数保护局部变量function fun(){    var n=0;    return function(){        return ++n;    }}//获得返回的内部函数对象var f1=fun();//实际上,var f1=function(){return ++n;}//使用内部函数对象操作受保护的变量console.log(f1());//1console.log(f1());//2n=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-1var name="global";var obj={    name:"local",    getNameFun:function(){        return function(){            return this.name;           };    }};console.log(obj.getNameFun()());//"global"//例6-2var 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)());//10console.log((foo.bar,foo.bar)());//10

七、闭包的问题

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

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

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

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

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

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

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

0 0
原创粉丝点击