闭包论
闭包论
今天打算,好好讨论一下javascript中强劲的闭包,虽然不清楚自己是否有资格讨论这个闭包的原理,但是我只想在此做一个总结来增加自己对闭包的理解;希望对新手和新手的我有作用!!
上午写了第一段开始到现在都没理清楚思路16:52:49。
我将以犀牛书书为主,然后参考别人一两篇博客为辅(主要是给简单的例子),来解析闭包原理.
我们来看看权威的犀牛书是怎么说闭包的:函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为'闭包'。
这句话涉及了2大知识点:作用域链,垃圾(内存)回收机制;为此我们分别来谈谈作用域,作用域链以及垃圾(内存)回收机制.
我们来复习一下3.10节和3.10.3节谈到的作用域和作用域链:
摘要重点文字如下。
作用域:一个变量的作用域是程序源代码中定义这个变量的区域(看清楚说的是定义)。全局变量拥有全局作用域,在任何地方都有定义。函数内部声明的变量只在函数体内有定义。在函数体内,局部变量优先级高于同名的全局变量。
作用域链:每一段javascript代码都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,这组对象定义了这段代码作用域中的变量。当需要查找变量x值的时候,它会从链中第一个对象开始查找,如果这个对象有一个名为x的属性,则直接返回这个属性,如果不存在,则继续查找链上的下一个对象。如果第二个也没有,则会继续查找下一个对象,以此类推,如果没有一个对象含有属性x,那么不存在x,最终抛出异常!
书上说作用域链的对象大多是指函数,属性也可以看做是函数的变量!函数之间有嵌套以及继承,从而有了上面说的对象列表(个人理解,如有不对请指出,谢谢)。
理解了上面的之后,接着理解嵌套函数词法作用域.看2个例子
1 var scope='global scope'; 2 function checkscope(){ 3 var scope='local1 scope'; 4 function f(){ return scope;} 5 return f(); 6 } 7 console.log(checkscope());
由于scope找到局部变量所以输出local1 scope。
1 var scope='global scope'; 2 function checkscope(){ 3 var scope='local1 scope'; 4 function f(){ return scope;} 5 return f; 6 } 7 console.log(checkscope()());
现在checkscope()返回一个函数对象,当在外部调用它时,scope的值依然是local1 scope。这就是闭包。闭包:函数定义时的作用域链到函数执行时依然有效。
再来看看一个网上的例子:
1 var name = "The Window"; 2 var object = { 3 name : "My Object", 4 getNameFunc : function(){ 5 return function(){ 6 return this.name; //这里this指向了window 7 }; 8 } 9 }; 10 alert(object.getNameFunc()()); //The Window
网上说理解了这段代码输出的原因就真正理解了闭包,但是我觉得这并不是很典型,因为这涉及了this的指向问题。如果不明白this指向的这题你再理解闭包也不知道结果的呀!!
this指向:this的值取决于调用上下文,如果一个函数不是作为某个对象的方法被调用,那么this就是global object.否则就是该对象。
所以,这里调用那个嵌套函数时候并没有作为某个对象的方法调用,故指向了global object。所以弹出The window!!
这里关于this问题不多说了!!接着讨论闭包!更深入的闭包问题!!
-----------------------------------------------------------------
为什么闭包可以保存局部变量的值?
了解了作用域链,我们再来看看js的内存回收机制,一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(自由变量))一起保存起来.也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收.
这段话来自墓中无人的博客
-------------------------------------------------------------------
function outerFun() { var a=0; function innerFun() { a++; alert(a); } return innerFun; //注意这里 } var obj=outerFun(); obj(); //结果为1 obj(); //结果为2 var obj2=outerFun(); obj2(); //结果为1 obj2(); //结果为2
在理解回收机制的同时,这里的结果也说明一个问题:每次调用outerFun()都会创建一个新的作用域链和一个新的私有变量。
------------------------------------------------------
这里再讨论一个网上很少说的知识点:在同一个作用域链中定义2个闭包,这2个闭包共享变量(这是非常重要的技术)。代码实例(见犀牛书)
function counter(){ var n=0; return { count:function(){ return n++;}, reset:function(){n=0;}; } } var c=counter(); c.count(); //0· c.count();//1 c.reset();//reset和count共享状态 c.count();//0
闭包还有一个问题,循环闭包:
function cons(){ var func=[]; for(var i=0;i<10;i++){ func[i]=function(){return i;} } return func; } var func=cons(); alert(func[5]());//10
记得之前看到一篇博客 很清楚讲了循环闭包,这里为什么是10。我简单说一下:主要是因为,当你在调用cons函数的时候里面的循环早就执行完了,然后形成了10个闭包,然后10个闭包里面的i沿着作用域链寻找i的值,此时i=10,故10个闭包中的i值都是10;正确使用循环闭包如下代码:
var func=[]; var i=100; for (var i=0; i<10; i++) { func[i]=function(i){ return function(){//这里不能加i 否则会报错undefined alert(i); } }(i) } func[5]();//5
每次循环都保存了 当前的i值,所以调用的时候会是5;
写一天了,累了!!!肯定有错误,希望你们指出一起学习啊!!