JavaScript中闭包的详解
闭包是什么
在JavaScript中,闭包是一个让人很难弄懂的概念。ECMAScript中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
是不是看完这个定义感觉更加懵逼了?别急,我们来分析一下。
- 闭包是一个函数
- 闭包可以使用在它外面定义的变量
- 闭包存在定义该变量的作用域中
好像有点清晰了,但是使用在它外面定义的变量是什么意思,我们先来看看变量作用域。
变量作用域
变量可分为全局变量和局部变量。全局变量的作用域就是全局性的,在js的任何地方都可以使用全局变量。在函数中使用var关键字声明变量,这时的变量即是局部变量,它的作用域只在声明该变量的函数内,在函数外面是访问不到该变量的。
varfunc=function(){ vara='linxin'; console.log(a);//linxin } func(); console.log(a);//UncaughtReferenceError:aisnotdefined
作用域相对比较简单,我们不多讲,来看看跟闭包关系比较大的变量生存周期。
变量生存周期
全局变量,生命周期是永久的。局部变量,当定义该变量的函数调用结束时,该变量就会被垃圾回收机制回收而销毁。再次调用该函数时又会重新定义了一个新变量。
varfunc=function(){ vara='linxin'; console.log(a); } func();
a为局部变量,在func调用完之后,a就会被销毁了。
varfunc=function(){ vara='linxin'; varfunc1=function(){ a+='a'; console.log(a); } returnfunc1; } varfunc2=func(); func2();//linxina func2();//linxinaa func2();//linxinaaa
可以看出,在第一次调用完func2之后,func中的变量a变成'linxina',而没有被销毁。因为此时func1形成了一个闭包,导致了a的生命周期延续了。
这下子闭包就比较明朗了。
- 闭包是一个函数,比如上面的func1函数
- 闭包使用其他函数定义的变量,使其不被销毁。比如上面func1调用了变量a
- 闭包存在定义该变量的作用域中,变量a存在func的作用域中,那么func1也必然存在这个作用域中。
现在可以说,满足这三个条件的就是闭包了。
下面我们通过一个简单而又经典的例子来进一步熟悉闭包。
for(vari=0;i<4;i++){ setTimeout(function(){ console.log(i) },0) }
我们可能会简单的以为控制台会打印出0123,可事实却打印出了4444,这又是为什么呢?我们发现,setTimeout函数时异步的,等到函数执行时,for循环已经结束了,此时的i的值为4,所以function(){console.log(i)}去找变量i,只能拿到4。
我们想起上一个例子中,闭包使a变量的值被保存起来了,那么这里我们也可以用闭包把0123保存起来。
for(vari=0;i<4;i++){ (function(i){ setTimeout(function(){ console.log(i) },0) })(i) }
当i=0时,把0作为参数传进匿名函数中,此时function(i){}此匿名函数中的i的值为0,等到setTimeout执行时顺着外层去找i,这时就能拿到0。如此循环,就能拿到想要的0123。
内存管理
在闭包中调用局部变量,会导致这个局部变量无法及时被销毁,相当于全局变量一样会一直占用着内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。
然而在使用闭包的过程中,比较容易形成JavaScript对象和DOM对象的循环引用,就有可能造成内存泄露。这是因为浏览器的垃圾回收机制中,如果两个对象之间形成了循环引用,那么它们都无法被回收。
functionfunc(){ vartest=document.getElementById('test'); test.onclick=function(){ console.log('helloworld'); } }
在上面例子中,func函数中用匿名函数创建了一个闭包。变量test是JavaScript对象,引用了id为test的DOM对象,DOM对象的onclick属性又引用了闭包,而闭包又可以调用test,因而形成了循环引用,导致两个对象都无法被回收。要解决这个问题,只需要把循环引用中的变量设为null即可。
functionfunc(){ vartest=document.getElementById('test'); test.onclick=function(){ console.log('helloworld'); } test=null; }
如果在func函数中不使用匿名函数创建闭包,而是通过引用一个外部函数,也不会出现循环引用的问题。
functionfunc(){ vartest=document.getElementById('test'); test.onclick=funcTest; } functionfuncTest(){ console.log('helloworld'); }
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持毛票票!