闭包是一个比较抽象的概念,尤其对js新手来说,书上的解释实在是晦涩,对我来说也是一样,但是它也是js提升中无法绕过的一环,几乎每次面试必问的问题,因为在回答的时候,你的答案的深度,对术语的理解以及js内部解释器的运作方式的描述,都是可以看出你js实际水平的,即使你没有答对,也能让考官对你的水平有个评估,那么我们先来说说我对js中的闭包的理解。
闭包是很多语言都具备的特性,在js中,闭包主要涉及到js的几个其他的特性:作用域链,垃圾(内存)回收机制,函数嵌套,等等。
在了解闭包以前,最好能先理解一下作用域链的含义,简单来说,作用域链是在函数定义的时候创建的,用于寻找使用到的变量值得索引,而它内部的规则是,把函数自身的本地变量放到最前面,把自身的父级函数中的变量放到其次,再把高一级函数中的变量放到更后面,以此类推直至全局对象为止,但函数中需要查询一个变量的值的时候,js解释器会去作用域链去查找,从最前面的本地变量中先找,如果没有找到对应的变量,则到下一级的链上找,一旦找到了变量,则不再继续,如果找到最后也没有找到相应的变量,解释器就返回undefined.
了解了作用域链,我们再来看看js的内存回收机制,一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所使用,等到函数执行完毕以后,这些变量就会认为是无用的了,对应的内存空间也就会被回收了,下次再执行此函数的时候,所有的变量又回到了最初的状态,重新赋值使用,但是如果这个函数内部又嵌套了另外一个函数,而这个函数是有可能在外部被调用到的,并且这个内部函数又使用了外部函数的某些变量的话,这种内存回收机制就会出现问题,如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到它所需要的外部函数中变量的值了,所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和祖先级函数的变量)一起保存起来,也就是构建一个闭包,这些变量将不会被内存机制所回收,只有当内部的函数不可能被调用以后(例如:被删除了,或者是没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收。
也就是说,有了闭包,嵌套的函数结构才可以运作,这也是符合我们预期的,然后,闭包还有一些特性,却往往让程序员觉得很难理解。
var result=[];
function foo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=function(){
alert(i)
}
}
};
foo();
result[0](); // 3
result[1](); // 3
result[2](); // 3
这段代码中,程序员希望foo函数中的变量i被内部循环的函数使用,并且能分别获得他们的索引,而实际上,只能获得该变量最后保留的值,也就是说,闭包中所记录的自由变量,只是对这个变量的一个引用,而非变量的值,当这个变量被改变了,闭包里获取到的变量值,也会被改变。
解决的方法之一,是让内部函数在循环创建的时候立即执行,并且捕捉当前的索引值,然后记录在自己的一个本地变量里,然后利用返回函数的方法,重写内部函数,让下一次调用的时候,返回本地变量的值,改进后的代码:
var result=[];
function foo(){
var i= 0;
for (;i<3;i=i+1){
result[i]=(function(j){
return function(){
alert(j);
};
})(i);
}
};
foo();
result[0](); // 0
result[1](); // 1
result[2](); // 2
在这里我再解释一下,这里用到了另外两个技术,立即调用的匿名函数和返回函数,也是初学者比较难以理解的部分。