JavaScript欲速则不达—JavaScript闭包

看完JavaScript欲速则不达—通过解析过程来了解作用域后我们知道,JavaScript中因为有作用域链的存在,在函数内部的变量在函数外部是无法访问的,在函数内部却可以得到外部变量。但是有时因为程序的需要,我们希望在外部函数访问到函数内部的变量,这就需要创建一种特殊的闭包来解决这个难题。

什么是闭包?

“官方”的解释是:

闭包就是能访问另一个函数作用域中的变量的函数。创建闭包的常见方式就是一个函数内部创建另一个函数。

这不是本文要讲的闭包,那本文要讲的闭包是怎么样的?看如下代码:

function outer() {
    var i = 5;
    return function inner() {
        alert(++i);
    }
}
var c = outer();
c();  //6
c();  //7

我们发现:函数outer内部包含函数inner(内部函数inner可以引用外部函数outer的变量和参数);函数outer返回函数inner;变量c又引用函数outer。

代码分析:函数inner从函数outer返回后,函数inner的作用域链就被初始化为包含函数outer的活动对象和全局变量对象。函数inner可以访问在函数outer定义的所有变量。显然作用域链是一个指向变量对象的指针列表,所以在将函数out赋值给c,外部变量c就指向了outer函数中返回的inner函数。c函数执行完毕之后,outer函数的活动对象不会立即销毁,因为其内部函数inner作用域链仍然在引用着这个活动对象。由于浏览器的回收机制,outer函数的活动对象会一直保存在内存中。

结论:
当内部函数作用域链引用了外部函数的活动对象的时候,并且在函数外部调用这个返回函数,就能得到我们想要的闭包的效果了。如下代码也能实现我们想要的效果:

    var age = 21;
    var o = (function(){
        var age=23;
        return{
            setAge : function(){
                alert(++age);
            }
        }
    }());
    o.setAge(); //24
    o.setAge(); //25
    

看了以上两个例子,有人就明白本文讲的是哪种闭包:在作用域之外的不能访问其内部var变量,而只能通过其内部方法访问。这样不同的对象有互不干涉,实现了面向对象中的对象。

所以最后我们得出结论:能创建新的作用域的就是闭包。而其中将内部函数返回,函数外部只能通过内部返回的方法才能访问内部var变量的闭包,能实现对内部var变量很好的封装,所以面试中经常会被问到此种闭包。

而闭包就只是内部函数的作用域链引用着外部函数活动变量,如下代码:

    var coluse = (function(){
        var i =0;
        function count(){
            alert(i++)
        }
        count(); // 0
        count(); // 1
    }())
    

因为浏览器的自动垃圾回收机制,被内部函数的作用域链指针引用着的内部变量,会一直存在与内存中。这就是闭包的原理,就这么简单。那浏览器的自动垃圾回收机制又是怎么个处理形式呢。

闭包的形成

为了节省内存,JavaScript具有自动垃圾回收机制。

javascript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占的内存,因为只要执行流进入相应的环境,就可能用到它们。而当变量离开环境时,这将其标记为“离开环境”。

自动垃圾回收机制在回收内存时,首先会判断该对象是否被其它对象引用。在确定没有其它对象引用便释放该对象内存区域。

利用这一点,创造了闭包这一特性。如上举例代码函数:执行 var c = a();时,c的执行环境会创建一个活动对象和一个作用域链,形如window<=b<=i。其局部变量i和函数b()都会挂在cc的活动对象中。当 var c = a();执行完毕后,执行环境会尝试回收活动对象占用的内存。但因return b,为其增加了一条作用域链:window=>b=>i,所以GC停止对b回收。因此如果想将一个局部变量/函数提升为全局的,为其增加一条作用域链自动回收机制就不会回收参数和变量了,也就形成闭包了。

闭包就是为本来就只有一条单向的作用域链创建了相反方向的,有因为最后指向了全局对象,所以自动垃圾回收机制没有将其回收

闭包就是利用变量一直被保存在内存中,所以会对内存造成消耗,所以不能滥用闭包。解决方法是 在退出函数之前,将不使用的局部变量全部删除。

闭包的使用

闭包的好处

  1. 保护函数内的变量安全。以第二个代码为例,只有通过o.setAge()才能访问变量age,而无法通过其他途径访问到,因此保护了内部变量,这就能更好的实现模块化,而不污染外部变量,实现封装。
  2. 在内存中维持一个变量。以最开始的例子为例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。这在获取索引值经常用到。
  3. 利用变量能长期驻扎在内存当中,而不被释放,可以做一个类似缓存的功能。

使用闭包的几个注意点

  • 闭包允许内层函数引用父函数中的变量,但是该变量是最终值。
  • 由于闭包函数会在不知不觉中创建出循环引用,而在闭包函数自己被释放前,我们很难判断父函数的参数以及它的局部变量是否能被释放。IE下会发生引发内存泄漏,当在DOM元素或一个ActiveX对象与普通JavaScript对象之间存在循环引用时,IE在释放这类变量时存在特殊的困难,最好手动切断循环引用,这个bug在IE 7中已经被修复了,而为了避免内存泄漏,我们只要手动解除即可,代码如下:
    window.onload = function(){
        var oDiv = document.getElementById('div1');
        var id  = oDiv.id;
        oDiv.onclick = function(){
        alert(id);
        }
        oDiv = null; //也可以用window.onunload = function(){ oDiv.onclick = null; }
    }
    

本文源链接:http://www.html5jscss.com/js-closure.html

转载请注明《JavaScript欲速则不达—JavaScript闭包》| html5jscss

评论关闭了.