ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
闭包是javascript的一个难点,也是一个重点,很多高级应用都需要用到闭包。在学习闭包的过程中看了不少书和大牛的博客,下面来说说我对闭包的理解和看法。 ### 函数 要说闭包就不得不先说一下函数,这个javascript的中流砥柱。 众所周知在ES6之前js都没有class这个关键字,一切的面向对象都是通过function模拟的。 一个简单到不能再简单的例子:函数内部可以访问外部的变量(全局变量)。 ~~~ var n=100; function f1() { console.log(n); } f1();//100 ~~~ <br> 而外部却不能访问函数内的局部变量。 ~~~ function f1() { var n=100; } console.log(n);//n is not defined ~~~ <br/> 那么问题来了,如果我想访问函数内的局部变量怎么办?一种解决办法就是使用闭包。 什么是闭包?我个人认为函数中的函数就是闭包,有点像java的内部类,既然函数可以访问外部变量,那么函数中的函数也可以访问外层函数的变量,这个内层函数就是闭包。 ~~~ function f1() { var n=100; function f2() { console.log(n); } f2(); } f1();//100 ~~~ 上述写法比较麻烦,因此可以把f2作为一个返回值返回给f1。 ~~~ function f1() { var n=100; function f2() { console.log(n); } return f2; } f1()();//100 //相当于var f=f1();f(); ~~~ <br> 下面来看一个问题:这里有5个li,名字是0到4,我希望我点击0的时候在控制台输出0,点击1的时候在控制台输出1,以此类推。 ~~~ var lis=document.getElementsByTagName("li"); for(var i=0;i<lis.length;i++) { lis[i].onclick=function() { console.log(i); } } ~~~ 奇怪的事情发生了,无论我点击哪一个在控制台输出的都是5。 这是为什么呢? 仔细观察,我为每个li创建了一个匿名函数,匿名函数形成了闭包,它们都在引用外部的i,当i改变时,自然所有匿名函数里面的i都改变了。用几张图来说明。 ![](https://box.kancloud.cn/2016-05-19_573d3ecda20ee.JPG) ![](https://box.kancloud.cn/2016-05-19_573d3ecdb94e8.JPG) ![](https://box.kancloud.cn/2016-05-19_573d3ecdce960.JPG) 当i增加到5的时候不再满足循环条件,由于五个匿名函数都是引用同一个i,所以它们打印出来的都是5。 既然它们是引用同一个i,那么只需为每一个i创建一个备份 ~~~ var lis=document.getElementsByTagName("li"); for(var i=0;i<lis.length;i++) { lis[i].onclick=function(num) { return function() { console.log(num); } }(i) } ~~~ 这里在匿名函数接收一个参数并且立即执行,匿名函数内部又创建了一个函数负责把这个参数返回给匿名函数,由于是按值传递的,所以相当于给i做了一个备份。就算i一直在改变,而每个匿名函数的num是固定的。 <br> ### 闭包中的this 在闭包中使用this会出现一些问题,在全局函数中this指向的是window,当函数以方法被调用时,this就指向这个对象。但是,匿名函数的执行环境具有全局性,this一般指向window。参考红宝石书中的一个经典的问题: ~~~ var name="The Window"; var object={ name:"My Object", getNameFunc:function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()());//"The Window" ~~~ 内部函数在搜索this的时候只会搜到其活动对象为止,由于匿名函数的执行环境具有全局性,因此当前的活动对象就是window。 <br> 可以通过改变当前的活动对象来改变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变量,因此当前的活动对象就从window变成了object。 <br> ### 闭包引发的问题 闭包虽好,但是也引发了一系列问题,尤其是内存泄漏。js的垃圾回收机制是引用计数的,当一个变量不再使用的时候(引用计数为0)垃圾回收机制就会把它清理掉。而闭包的存在会导致引用数至少为1。 ~~~ function f() { var myDiv=document.getElementById("Div1"); myDiv.onclick=function() { console.log(myDiv.id); }; } ~~~ 上述代码中,函数f需要等myDiv清除后才能被清除,而myDiv由于匿名函数的存在,它的引用数至少为1,因此它所占用的内存永远不会被回收。 <br> 解决办法: ~~~ function f() { var myDiv=document.getElementById("Div1"); var id=myDiv.id myDiv.onclick=function() { console.log(id); }; myDiv=null; } ~~~ 把myDiv.id保存在一个变量中,并且在闭包中引用这个变量,这样就与myDiv没有关系了。但是闭包会引用包含函数的整个活动对象,因此必须把myDiv设为null来解除对它的引用,确保垃圾回收机制能够把资源回收。 <br> 使用闭包应该注意的问题:闭包虽然好,但是在实际应用中尽量减少 闭包的使用,并且为了防止内存泄漏,要时刻记得在闭包完后清除它的局部变量。