在js中,闭包是一个很重要又至关不容易彻底理解的要点,网上关于讲解闭包的文章很是多,可是并非很是容易读懂,在这里以《javascript高级程序设计》里面的理论为基础。用拆分的方式,深刻讲解一下对于闭包的理解,若是有不对请指正。javascript
闭包的内部细节,依赖于函数被调用过程所发生的一系列事件为基础,因此有必要先弄清楚如下几个概念:前端
- 执行环境(execution context)定义了变量或者函数有权访问的其余数据,每一个执行环境都有一个与之关联的变量对象(variable object),执行环境中定义的变量和函数就保存在这个变量对象中;
全局执行环境是最外围的一个执行环境,一般被认为是window对象
执行环境和变量对象在运行函数时生成
执行环境中的全部代码执行完之后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)java
当代码在一个执行环境中执行时,会建立变量对象的一个做用域链(scope chain),做用域链用来指定执行环境有权访问的全部变量和函数的访问顺序;
做用域链的最前端,始终是当前代码执行环境的变量对象,若是这个环境是函数,则其活动对象就是变量对象
做用域链的下一个变量对象,来自外部包含环境,再下一个变量对象,来自下一个外部包含环境,以此类推直到全局执行环境
在函数执行过程,根据当前执行环境的做用域链来逐层向外查找变量,而且进行标识符解析数组
是否是以为以上的理论很枯燥并且艰涩?由于基本上是从书上引用来的,不着急着理解,先摆在上面,等会结合案例回头再来看!接下来请看样例:闭包
样例1 <script> var a = 2; function A(){ var a = 1; return a ; } console.log(A());//1 </script>
以这段简单的代码为例,根据上面的理论画一下关系图(直接用ps画的,原谅我拙劣的笔迹):
如图所示,在执行函数A的时候,建立了A的执行环境和变量对象,其中A的变量对象和全局变量对象中都含有a变量,根据做用域链从前向后查找,在A的变量对象中找到,因此输出1,执行完毕之后 ,A的执行环境销毁,A的变量对象因为没有被引用,因此也销毁;函数
样例2 <script> function A(){ var a = 1; return a ; } console.log(a);// 报错 a is not defined </script>
这个例子比较简单,要画图的话只须要画一个全局变量对便可,由于在js中,外围环境没法访问内围局部变量(其实本质就是做用域链上找不到相应的值),因此这里会报变量未定义的错误。spa
样例3 <script> function A(){ var a = 1; function B(){ if(a==1){ console.log(1) } else { console.log(0); } } B(); } A();//1 </script>
上面这个例子,在函数A中定义了函数B,关系图以下:
从图上能够很清楚的看出,在每一个执行环境中能够访问到的变量对象,因此B能够访问A的变量对象和全局变量对象中的变量以及自身变量对象,A能够访问自身变量对象和全局变量对象设计
关于执行环境和做用域链暂时说到这里,下面进入正题,讲闭包;3d
闭包是指有权访问另外一个函数做用域变量的函数,建立闭包的一般方式,是在一个函数内部建立另外一个函数code
上文咱们提到了,因为做用域链的结构,外围函数是没法访问内部变量的,为了可以访问内部变量,咱们就可使用闭包,闭包的本质仍是函数,闭包的本质仍是函数闭包的本质仍是函数。
样例4 <script> function A(){ var x = 1; return function(){ console.log(x); } } var m = A(); m();//1 </script>
上面就是一个很简单的闭包例子,经过m函数,咱们能够得到A函数内部变量的值,这个样例比较简单,看不出什么问题,接下来咱们来深刻了解一下。
-------------------------------从简单到复杂的分割线,请作好准备----------------------------------------------------
样例5 <script> function A(){ var x = 1; return function(){ x++; console.log(x); } } var m1 = A();//第一次执行A函数 m1();//2 m1();//3 var m2 = A();//第二次执行A函数 m2();//2 m1();//4 </script>
上面这个例子其实能够引出几个问题:
1.为何连续执行m1的时候,x的值在递增?
2.定义函数m2的时候,为何x的值从新从1开始了?
3.运行m2之后,为何再运行m1,x仍是按照以前m1的运行结果继续增加?(其实就是m1和m2里面的x为何是相互独立,各自维持的?)
其实要解决上面的问题,咱们就要用到前面铺垫的知识点了:
首先,先画一下结构图,
(额,这图画的可能真的有点丑),不要慌,图上虽然画的有点乱,可是其实很简单:左半部分和上面简单闭包的例子,实际上是彻底同样的,而右边半部分,与左边实际上是彻底对称的;注意看图上的重点:每次执行A函数时,都会生成一个A的活动变量和执行环境,执行完毕之后,A的执行环境销毁,可是活动对象因为被闭包函数引用,因此仍然保留,因此,最终剩下两个A的变量对象,所以m1和m2在操做x时,指向的是不一样的数据,
如今来回答上面的三个问题:
1.(为何连续执行m1的时候,x的值在递增?)
answer:由于m1在引用的活动对象A一直没有释放(想释放的话可让m1=null),因此x的值一直递增。
2.定义函数m2的时候,为何x的值从新从1开始了?
answer:由于又一次运行了A函数,生成一个新的A的活动对象,因此m2的做用域链引用的是一个新的x值。
3.m1和m2里面的x为何是相互独立,各自维持的?
answer:由于在定义m1和m2的时候,分别运行了A函数,生成了两个活动对象,因此,m1和m2的做用域链是指向不一样的A的活动对象的。
好的,到这里先回顾一下前面说到的知识点:
执行环境和变量对象在运行函数时生成
执行环境中的全部代码执行完之后,执行环境被销毁,保存在其中的变量和函数也随之销毁;(全局执行环境到应用退出时销毁)
感受理解了吗?接下来,再看看另外一个很相似的例子:
样例6 <script> function A(){ var x = 1; var m=[]; m[0] = function(){ x++; console.log(x); }; m[1] = function(){ x++; console.log(x); } return m; } var m = A();//第一次运行A,并且只运行这一次 m[0]();//2 m[1]();//3 m[0]();//4 m[1]();//5 </script>
这个例子和刚刚十分相似,不一样的是,在A内部就先定义了两个函数,能够看出 ,最后的结果与上面的例子有些不一样:
变量x仍然能保持递增,可是m[0]和m[1]定义的函数,对于x的改变再也不是相互独立的,其实你们估计猜到了,这里的m[0]和m[1]的做用域指向的A的变量对象,实际上是同一个,为何呢?很简单,看看刚刚这段代码,实际上是只调用了一次A函数,再看上文那句话:
执行环境和变量对象在运行函数时生成
既然A只执行一次,那么A的活动变量固然也就生成了一个,因此这里m[0]和m[1]的做用域指向同一个A的变量对象
样例7 <script> function A(){ var funs=[]; for(var i=0;i<10;i++){ funs[i]=function(){ return i; } } return funs; } var funs = A();//定义funs[0]-funs[9],10个函数 console.log(funs[0]());//10 console.log(funs[1]());//10 console.log(funs[6]());//10 </script>
这个例子其实算是一个经典案例,在不少地方都有提到,执行完毕后 funs数组中,funs[0]-funs[9]存的其实都是同样的,都是一个返回i值的函数,这个例子容易错误的地方其实在于,弄错了产生执行环境的时机,仍是看这句话:
执行环境和变量对象在运行函数时生成
因此,当执行 var funs = A();
时,只是定义函数,而没有执行,真正产生环境变量的时间是在console.log(funs[0]());
这三句的时候,此时A的变量对象中i值是什么呢?很简单,看它return的时候,i的值,显然,i的值是10,因此,最后三句输出的都是10
好的,针对以上的案例,若是我就是想让fun[i]可以返回i,那应该怎么写呢?在《javascript高级程序设计》中,提供了一种参考的写法:
样例8 <script> function A(){ var funs=[]; for(var i=0;i<10;i++){ funs[i] = function anonymous1(num){ return function anonymous2(){ return num; } }(i); } return funs; } var funs = A();//定义funs[0]-funs[9],10个函数 console.log(funs[0]());//0 console.log(funs[1]());//1 console.log(funs[6]());//6 </script>
是否是一看头就大了?不要紧,接下来咱们慢慢分析,固然,上述代码中anonymous1和anonymous2两个名字是我本身添加上的,为了后面可以更好的说明。
首先,先来看看function anonymous1(num){}(i),这是一个当即执行函数,效果和名字同样,定义完以后立刻运行结果,那这里运行的结果是什么呢?就是把i的值当即传递给num这个局部变量,而后再返回anonymous2,请注意这个当即执行函数被执行的次数,10次,再来看看这句话
执行环境和变量对象在运行函数时生成
好的,那如今请回答我:
这里面生成了几个anonymous1的活动变量?
answer:固然也是10个,
那每一个anonymous1活动变量中存贮的num值是多少?
answer:看anonymous函数return的时候能够知道,存贮的num值就是每次传入的i值,也就是0-9
好了,那如今很明了了,这样的写法其实至关于,把每次的i值都保存在一个anonymous1活动变量钟,给最内层的anonymous2函数使用
写到这里,关于闭包的主要特征和辨别方式已经基本讲到了,我的感受由于这个问题比较抽象,仍是多看看文中以及网上的一些例子,加深理解。以上内容属于我的看法,若是有不一样意见,欢迎指出和探讨。但愿能对看到的人有所帮助,同时,码字不易(尤为是还要配上灵魂画师级别的配图~),请尊重做者的版权,转载请注明出处,如做商用,请与做者联系,感谢!