谈谈闭包

写在前面:本文案例除特别指明,默认基于chrome mac版。你可能想:闭包么难道还有浏览器差异么?!嘿嘿,你且看来:

按我的理解,闭包就是可以在函数外面访问其内部变量的一种方式。因为大家都知道,javascript执行时遵循一个隐式的作用域链,查询变量的值时将沿作用域链一路向上查找。
先看看常规方法和闭包的不同:

  1. var i='global';
  2. var func=function(){
  3. console.log(i);
  4. }
  5. func();
此时的输出为'global'。

这就是常规作用域链的最简单的例子,当我们在执行func()方法时,因为其内部并没有定义i,所以它开始向上查找i值,最终在全局环境中找到i.

  1. var i='global';
  2. function constfuncs(){
  3. var i=10;
  4. var func=function(){
  5. console.log(i);
  6. }
  7. return func;
  8. }
  9. var f=constfuncs();
  10. f();
此时的输出为10。

此例中f函数和上例中的func函数一样,在执行时都没有在自身的上下文中找到i值。但不同的是,func函数向上一级查找是到全局环境,而此例中f方法却找到了constfuncs中。啊哈,这就是今天讲的闭包。闭包可以访问到另一个函数的局部变量呢~
在js中,如果函数执行完,正常情况下,该函数的局部变量将被销毁,外界并不会访问到该函数中的局部变量,比如:

  1. function a(){
  2. var i=10;
  3. console.log(i);//10
  4. }
  5. console.log(i);//undefined

但是本例中的constfuncs因为返回了一个函数变量,且该函数变量中又引用了constfuncs函数中的i,所以此时constfuncs在执行结束后,其中的变量并不会被销毁,而是要为它返回的函数"留着"。


且看下面的截图:
当f函数执行到console.log(i)时,它对应的上下文在调试器中非常清楚:Local指该函数的局部变量,Closure指的就是闭包变量,而Global为全局变量。此时找i值的顺序就是Local || Closure || Global
如果i值不是一个恒定的值,而是在外层函数执行中进行了一系列的变化,那么当调用返回的函数时,又执行了什么操作呢?

  1. function constfuncs() {
  2. var funcs = [];
  3. for (var i = 0; i < 10; i++) {
  4. funcs[i] = function () {
  5. return i;
  6. }
  7. }
  8. return funcs;
  9. }
  10. var funcs = constfuncs();
  11. alert(funcs[1]());
此时的输出为10.

其实仔细来推敲一下,当向funcs数组添加项时,每项都是function(){console.log(i)},这里i值只是简单指向外层的i值。当funcs1执行时,用到i值了,它开始去找闭包变量中的i,而此时i值已经固定,为10。所以,不管执行funcs中的哪一项,它用到i值时,都只能得到最终的i值。
打断点看清函数在执行时的上下文:Closure中的i值为10。

那么如何才能使funcs中的每项执行的时候,能用到"被定义时的真实环境"信息呢?也就是,"切断"与初始值i的一些联系,因为i最后又只是一个最终值....看下面 这样行不行:

  1. function constfuncs() {
  2. var funcs = [];
  3. for (var i = 0; i < 10; i++) {
  4. var v = i;
  5. funcs[v] = function () {
  6. return v;
  7. };
  8. }
  9. return funcs;
  10. }
  11. var funcs = constfuncs();
  12. console.log(funcs[1]());

我没得到一个i值就把它存成v,然后使用v来给funcs赋值,最后我再执行funcs中的函数时,不就不去访问i了嘛?就是访问v了,而每次都新建一个v呢~如果外层函数constfuncs每次都因为闭包的原因把v存起来,我最后不是会有很多个v嘛?我好聪明耶!我都脑洞大开想了一通了,来实践一下:

结果....结果很让我失望,输出了9.....囧

为啥呢?
回头又仔细推敲下我刚才说的,存很多个v??

人家为啥要给你存很多个 v啊?当constfuncs函数结束执行后,它会把闭包中要用到的变量存起来没错,但是人家不能记着执行过程都给你存着。注意:我之所以做这个实验是因为v和i我认为是不同的,v是每次都新建一个,而i值是重复改写。浏览器说,你再来感受下:

  1. function constfuncs() {
  2. var i=10,j=30;
  3. var func=function(){
  4. return i;
  5. }
  6. return func;
  7. }
  8. var f = constfuncs();
  9. console.log(f());


这里又展现出了浏览器的不妥协:我不光给你存个最终值,我连你用不到的值也不存。额...你好机智..
等等,我们看看其他浏览器的处理,safari是这样的:

firefox和chrome是一样的处理方式,即能少则少:

说了这么多,我们还没达到目的呢,刚才那个方法还是被i牵着鼻子走呢?再来!

  1. function construct(v) {
  2. return function(){
  3. return v;
  4. }
  5. }
  6. function constfuncs() {
  7. var funcs = [];
  8. for (var i = 0; i < 10; i++) {
  9. funcs[i] = construct(i);
  10. }
  11. return funcs;
  12. }
  13. var funcs = constfuncs();
  14. console.log(funcs[4]());
哇哈!输出4!

这里的关键在于,我把i作为参数传给construct函数声称了一个新函数并赋予funcs中的每一项,而construct返回的每个函数都是有其单独的闭包环境的,就是construct构造的闭包环境啦。且看:

所以,当闭包形成时,所有被用到的局部变量都被保存,参数也是如此。

总结

* 闭包,可以说是一个封闭的环境,对外给出的函数就像一个口子,打开闭包外访问内部的一个门。
* 当闭包会保存外部可能访问到的其内部的一切值,当然包含其外层函数的参数。
* 根据浏览器的不同,当产生闭包时,值的保留有些会全部保留(如safari),有些会保留用到的值(如chrome 和 firefox) 。

感谢看到最后~本人不才,还望能助你理解一二。如有不同见解,还请留言。





posted @ 2015-06-21 21:40  rubyisapm  阅读(203)  评论(0编辑  收藏  举报