再说闭包
发布在没事瞎琢磨2015年6月12日view:5775闭包&作用域
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。
#写在前面:本文案例除特别指明,默认基于chrome mac版。你可能想:闭包么难道还有浏览器差异么?!嘿嘿,你且看来:

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

var i='global';
var func=function(){
   console.log(i);
}
func();
此时的输出为’global’。

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

var i='global';
function constfuncs(){
   var i=10;
   var func=function(){
       console.log(i);
   }
   return func;
}
var f=constfuncs();
f();
此时的输出为10。

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

function a(){
    var i=10;
    console.log(i);//10
}
console.log(i);//undefined

但是本例中的constfuncs因为返回了一个函数变量,且该函数变量中又引用了constfuncs函数中的i,所以此时constfuncs在执行结束后,其中的变量并不会被销毁,而是要为它返回的函数"留着"。 且看下面的截图: enter image description here 当f函数执行到console.log(i)时,它对应的上下文在调试器中非常清楚:Local指该函数的局部变量,Closure指的就是闭包变量,而Global为全局变量。此时找i值的顺序就是Local || Closure || Global 如果i值不是一个恒定的值,而是在外层函数执行中进行了一系列的变化,那么当调用返回的函数时,又执行了什么操作呢?

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = function () {
            return i;
        }
    }
    return funcs;
}
var funcs = constfuncs();
alert(funcs[1]());
此时的输出为10.

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

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

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        var v = i;
        funcs[v] = function () {
            return v;
        };
    }
    return funcs;
}
var funcs = constfuncs();
console.log(funcs[1]());

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

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

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

function constfuncs() {
    var i=10,j=30;
    var func=function(){
        return i;
    }
    return func;
}

var f = constfuncs();
console.log(f());

这里又展现出了浏览器的不妥协:我不光给你存个最终值,我连你用不到的值也不存。额...你好机智.. 等等,我们看看其他浏览器的处理,safari是这样的: enter image description here firefox和chrome是一样的处理方式,即能少则少: enter image description here 说了这么多,我们还没达到目的呢,刚才那个方法还是被i牵着鼻子走呢?再来!

function construct(v) {
   return function(){
       return v;
   }
}
function constfuncs() {
   var funcs = [];
   for (var i = 0; i < 10; i++) {
       funcs[i] = construct(i);
   }
   return funcs;
}
var funcs = constfuncs();
console.log(funcs[4]());
哇哈!输出4!

这里的关键在于,我把i作为参数传给construct函数声称了一个新函数并赋予funcs中的每一项,而construct返回的每个函数都是有其单独的闭包环境的,就是construct构造的闭包环境啦。且看: enter image description here 所以,当闭包形成时,所有被用到的局部变量都被保存,参数也是如此。

总结

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

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

评论
发表评论
4年前
添加了一枚【评注】:请问这个调试器是什么来的呀?我用了 chrome 与 firefox debugger 都看不到这个scope variables 的执行过程。
4年前
添加了一枚【评注】:请问这个调试器是什么来的呀?我用了 chrome 与 firefox debugger 都看不到这个scope variables 的执行过程。
4年前
添加了一枚【评注】:v在一个函数作用域下,重复声明相当于再赋值
4年前
添加了一枚【评注】:汗,这不是闭包的定义啊
4年前

有点收获

4年前

大赞

WRITTEN BY
rubyisapm
作为一个前端,尤其是前端妹纸,一定要...优雅~
TA的新浪微博
PUBLISHED IN
没事瞎琢磨

没事瞎琢磨…

我的收藏