深入理解JS—闭包

上次和大家聊过作用域后,了解了变量的作用域,并且知道如何从外部读取局部变量,那么接下来让我们通过一个实例来回顾下:
function f1(){
    n=999;
    function f2(){
      alert(n); // 999
    }
  }
我们知道函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!所以在上述代码中,变量n实际上是一个全局变量,其中函数f2被包括在函数f1中,这时f1z内所有的局部变量对于f2是可见的,但是对于f2而言,并不是所有的局部变量对于f1都是可见的,这也是我们所称的“链式作用域”。其中,子对象会一级一级地向上寻找所有父对象的变量,所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){
    n=999;
    function f2(){
        alert(n);
    }
    return f2;//将f2当做对象返回
}
var result=f1();
result();//999
从上述实例中,我们所见到的能够读取其他函数内部变量的函数的现象就是我们要谈的闭包。
那么闭包只有这一点作用吗?
其实,不然,闭包的作用主要体现在如下两点:
**一个是前面提到的可以读取函数内部的变量;
**另一个就是让这些变量的值始终保持在内存中。
闭包一般有如下两种情况:
第一,函数作为返回值
function fn(){
    var max=10;
    return function bar(x){
        if(x>max){
            console.log(x);
        }
    };
}
var f1=fn();
f1(20);
如上代码,bar函数作为返回值,赋值给f1变量。执行f1(20)时,用到了fn作用域下的max变量的值。
第二,函数作为参数被传递:
var max=10,
    fn=function(x){
        if(x>max){
            console.log(x);
        };
    }
    (function (f){
        var max=100;
        f(20);
    })(fn);
    //20
分析:在上述代码中,fn函数作为一个参数被传递到另一个函数,赋值给f参数,执行f(20)时,max变量的取值时10,因为在VO中,如果变量名有冲突,会选择忽略,因此忽略100,(具体顺序规则见上篇作用域部分)
而(function (f){
var max=100;
f(20);
})(fn);整个部分可以看做是匿名函数,所以匿名函数本身也是个闭包的。

使用闭包带来的问题

万事有弊有利,闭包也一样,在使用闭包时也会带来一些问题,这也是我们需要注意的地方。
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
那么使用闭包为什么会增加内容开销呢?
让我们以一个实例来看下:

image

第一步,代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值。此时全局上下文环境是活动状态。

image

第二步,执行第17行代码时,调用fn(),产生fn()执行上下文环境,压栈,并设置为活动状态。

image

第三步,执行完第17行,fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是这里不能这么做。注意,重点来了:因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的作用域。而正巧合的是,返回的这个函数体中,还有一个自由变量max要引用fn作用域下的fn()上下文环境中的max。因此,这个max不能被销毁,销毁了之后bar函数中的max就找不到值了。
因此,这里的fn()上下文环境不能被销毁,还依然存在与执行上下文栈中。
即,执行到第18行时,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。如下图:

image

第四步,执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态。

image

执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10。这个过程在作用域链一节已经讲过。
这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。
第五步,执行完20行就是上下文环境的销毁过程。
根据上述分析可知使用闭包会增加内容开销,很容易导致内存泄漏。



PS:以上图片代码分析来自参考javascript原型和闭包博客文章,其他地方如有误,请指正,谢谢!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值