学习javaScript已经有一段时间了,在这段时间里,已经感受到了JavaScript的种种魅力,这是一门神奇的语言,同时也是一门正在逐步完善的语言,相信在大家的逐步修改中,这门语言会逐步的完善下去,在上一篇随笔中,和大家分享了JavaScript中独有的类中的继承方式,今天呢,就跟大家分享一下我这几天一直在搞,却还是搞的不是很透彻的闭包问题,对于一个初学者而言,JavaScript中的闭包无疑是一个难点,而且也是我们必须要掌握的一个重点,那么今天我就跟大家分享一下我在学习闭包的时候的感悟以及相应的理解,首先,我们在一个函数中有一个变量: 代码1
1 function people(){ 2 var age = 12;//函数中有一个变量 3 ( function (){ //在函数中我们再定义一个自调用函数 4 alert(age); 5 })(); 6 7 }
在函数people中,有着变量age,大家应该之前都有了解到JavaScript中特有的变量作用域的问题,函数本身就是一个作用域,函数里边可以访问外边的变量,而函数外边并不能访问函数里边的变量,这样,我们就遇到了一个问题,如果我们想要获取函数中的变量age怎么办?如代码1中所示,因为自调用函数在people中,所以它可以访问它外边的变量,所以自调用函数可以访问age,那么我们就想,将这个自调用函数return出来不就行了吗?代码2:
1 function people(){ 2 var age = 12; 3 return function (){ 4 alert(age); 5 } 6 } 7 var xiaoMing = new people(); 8 xiaoMing();//会弹出文本框12,说明在people外边调用到了内部变量age
好了,通过这种方法,我们获得了函数内部的变量,那么这就叫做闭包(closure),上边的功能就是它的第一个功能,可以在函数外部通过闭包获得函数内部的变量,从形状上看,闭包就是函数里边再定义一个函数并返回,从书上以及各个博客上看到了对闭包的解释各种各样,我对闭包的理解而言,整个函数就相当于一个房子,这个房子没有门,只有一个天窗,我们在下边无法看见房子里边放的东西,而闭包就像是一个梯子,让我们爬上去可以通过天窗看到房子里边放的东西,就是连接函数外边和里边的一座桥梁,闭包对于我们的便利性是不言而喻的,然而,事物总是有着相反的一面,闭包有他的好,当然也有它的坏,现在,我们可以先用闭包做一个累加的例子:代码3:
1 function func(){ 2 var count=10; 3 return function(){ 4 return ++count; 5 } 6 } 7 var ss = new func(); 8 alert(ss());//弹出11 9 alert(ss());//弹出12
通过代码3我们可以看出,每次调用func中返回的闭包时,count是在累加的,这时我们心中肯定就会有很多疑惑,不是说好的函数是一个作用域吗?执行完了函数func它体内的变量应该不存在了呀?怎么每次调用的时候它都在累加呢?这就是闭包的第二个作用了,当闭包在函数体外执行时,它的体内保存了原来算是它的父类的函数的整个执行环境以及它体内调用的变量,这时他们的关系就像我们盖房子一样,func就像地基一样,ss就是我们的房顶,这时我们站在房顶上,那么地基会消失吗?显然是不能的,这时闭包的一个优点,同时也算是它的一个缺点,就是它将一个内部变量一直放到了内存中释放不了,有可能会造成内存泄露,但这也是它的一个优点,可以将作用域链拉长,但这个功能在for循环中是一个经常出错的地方:
代码4:
1 function func(){ 2 var arr=[]; 3 for (var i=0;i<5;i++){ 4 arr[i]=function (){//注意此处,往数组中存放的是一个函数 5 return i; 6 } 7 } 8 return arr; 9 } 10 var arr=func(); 11 for(var i=0;i<5;i++){ 12 console.log(arr[i]());//这时会返回五个5 13 }
这时我们是否也会感到同样的疑惑,这不是应该返回0-4吗?这正是闭包在应用中的一个坑,这时,我们可以这样理解,当往arr[0]中存放第一个闭包函数时,函数并没有执行,那么这时闭包中就存放了i的一个链接,它也不知道i的值是多少,当func执行完了,这时闭包中存放在i的一个作用域链接,当执行arr数组中的函数时,由于数组中的函数没有i这个变量,所以它要向上一级去寻找,这时,它就找到了func中,而此时func中i的值已经变为5了,所以输出的每一个数都是5.那么有因就有果,有好就有坏,有错误我们就有方法去解决:
1 function func(){ 2 var arr=[]; 3 for (var i=0;i<5;i++){ 4 arr[i]=(function (num){ 5 return i; 6 })(i);//这时使用自调用函数 7 } 8 return arr; 9 } 10 var arr=func(); 11 for(var i=0;i<5;i++){ 12 console.log(arr[i]);//这时会输出0-4 13 }
修改的方法一:就是将闭包改为一个自调用函数这时,传入arr[i]中的不再是一个函数,而是传入的参数i,以此类推,我们还可以返回一个闭包,只不过要让闭包中存放一个确定的值,而不再是一个变量:
1 function func(){ 2 var arr=[]; 3 for (var i=0;i<5;i++){ 4 arr[i]=function (num){ 5 return function(){ 6 return num; 7 } 8 }(i); 9 } 10 return arr; 11 } 12 var arr=func(); 13 for(var i=0;i<5;i++){ 14 console.log(arr[i]());//这时会输出0-4 15 }
使用这种方案的时候,用新创建的函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,所以在使用闭包时,尽量不要将后续会发生变化的变量放到闭包中.同时,闭包不仅会改变函数中的变量的使用范围变广,它同时也会改变this的作用域:
1 var name="kkkk"; 2 function People(){ 3 this.name="lala", 4 this.getName=function(){ 5 return function(){ 6 alert(this.name) 7 }; 8 } 9 } 10 var xiaoMing=new People(); 11 xiaoMing.getName()();//这时会弹出kkkk
这时,当我们返回一个闭包时,它当中存放的this绑定的对象是window,而不是People,其中的原理:我的理解就是,当你返回一个函数时,它其中的this是封闭在其中的那时候还没有执行函数,所以this的指向还没有确定,当在对象外执行闭包时,this回去找它要指向的对象,这时它就会指向window,我们可以用that来捕获this:
1 var name="kkkk"; 2 function People(){ 3 this.name="lala", 4 this.getName=function(){ 5 var that=this; 6 return function(){ 7 alert(that.name) 8 }; 9 } 10 } 11 var xiaoMing=new People(); 12 xiaoMing.getName()();
这时再输出就是lala了,当然,我们不仅可以使用这种方式来捕获this,我们还可以使用之前所学的对象冒充的方法来执行函数:
1 var name="kkkk"; 2 function People(){ 3 this.name="lala", 4 this.getName=function(){ 5 return function(){ 6 alert(this.name) 7 }; 8 } 9 } 10 var xiaoMing=new People(); 11 xiaoMing.getName().apply(xiaoMing,[]);
使用这种方式同样可以达到我们想要的目的,同时,我们使用call也是可以的.到了这里,大家应该对闭包有了一个初步的了解吧,闭包这种东西,可能理解的时候好理解一些,但是当我们实际应用的时候,刚开始可能会感觉到十分的吃力,但我们应该相信,熟能生巧,而且闭包这种东西,可能我们每天都在用,可对于它内在的原理还是一知半解,这个一定要多做练习,多做实验,当你有疑问的时候,就把代码打下来去运行一下,看一下结果,再想一下为什么会是这种结果,大家,一起加油!
本博文是博主原创,如有转载请说明出处!