冠军

导航

javascript 中的暗物质 - 闭包

1. 诡异的闭包

javascript 中有一个特殊的特性 - 闭包,对于 .NET 程序员来说,比较熟悉的是面向对象的程序设计 OOP,  而来自函数式语言的闭包则显得比较诡异,许多程序员对它敬而远之。

对于闭包我们还是要从函数式语言的特点说起。

不知道你有没有发现,在 javascript 中没有 public ,private 之类的关键字,也没有 class ,虽然也存在对象一说,但是对象的地位远远没有在 C# 中是一等公民,在 js 中,没有对象你也可以一样写程序。它只是一种数据的表示形式而已,可有也可无。

2. 闭包何来?

如何在 javascript 实现数据的保护呢?闭包就是实现它的利器,这需要我们放下普通的对象,理解一下 javascript 的工作原理。

在 javascript 中,可以在函数中定义新的函数,这种嵌套函数还可以作为函数的返回值,被外部的变量所引用。在普通的程序设计语言中,比如 C 中,虽然也存在函数指针的概念,但是,所谓的函数指针仅仅是一段代码的地址而已,而 javascript 中返回的函数引用,则不限于此。

在 C 语言中,在函数运行的时候,局部变量是保存在堆栈中的,函数执行完毕,系统所做的是弹出堆栈。

实际上,在 javascript 中,函数每次执行的时候,注意是运行时,系统会同时创建一个此次函数运行的环境对象,而此次运行期间的局部变量则关联在这个环境对象上,在普通不返回函数的普通函数中,函数执行完毕,则环境对象也一起释放。而如果函数返回了定义在外部函数中的嵌套函数,那么,这个环境对象将不会释放,也就是说,这个时候,返回了一个看得见的函数对象,还附带了一个看不见的暗物质 - 外部函数的环境对象。

看得见的函数对象加上隐含的环境对象就是闭包。

这个环境对象只能通过这个函数隐式访问,我们并没有它的引用,也无法直接访问它。结果就是实现了信息的隐藏。

3. 实现私有的数据

考虑下面的代码

function outer() {
    var name = "Alice";
    var inner = function () {
        return name;
    }
    return inner;
}

var fn = outer();
alert(fn());

在这个例子中,看起来简单的 fn 函数背后,其实暗藏了在执行 outer 函数时期创建的环境对象,所以通过 fn 可以得到 Alice 这个名字,而且没有其他的渠道允许得到这个名字。

4. 为什么数据搞乱了?

再看另外一个经典的例子。

<body>
    <div>
        <a href="#">Click Me!</a>
        <a href="#">Click Me!</a>
        <a href="#">Click Me!</a>
    </div>
    <script type ="text/javascript">
        function main(links) {
            for (var i = 0; i < links.length; i++) {
                links[i].onclick = function () {
                    alert(i + 1);
                }
            }
        };
        main(document.getElementsByTagName("a"));
    </script>

</body>

弹出的是多少呢?感觉有三次循环,应该弹出 1, 2, 3。运行一下,你会看到实际上是 4, 4, 4!

是不是非常诡异?

从闭包的角度来说,则非常简单,main 函数执行了几次呢?只有一次,在执行的时候创建了一个闭包对象,其中引用了定义在 main 中的局部变量 i,在循环体中,实际上创建了三个内部函数,它们引用的都是同一个环境对象。这些函数注册到链接的 onclick 事件上,其实也就是已经传出了函数 main,所以,main 的环境对象也就悄悄地成为了暗物质,而循环完成之后 i 已经最终被赋予了 3 这个值,三个函数访问的是同一个环境对象中的 i, 所以,在点击链接的时候看到 4 这个结果也就正常了。

5. 解铃还需系铃人

如果希望得到的是  1, 2, 3 这个结果又该怎么办呢?

我们可以定义一个内部函数,让它执行三次,这个将会创建三个对应的环境对象,我们可以使得这三个环境对象包含不同的值。

function main(links) {
    var inner = function (elem, i) {
        elem.onclick = function () {
            alert(i + 1);
        };
    };
    for (var i = 0; i < links.length; i++) {
        var elem = links[i];
        inner(elem, i);
    }
};

由于 inner 函数执行了三次,所以将会创建三个不同的环境对象,每个环境对象中的  i 都是不同的值。这样注册到 onclick 中的函数就可以访问到不同的值了。

闭包的概念

这里,我们可以看一下闭包的概念了。来自 Wiki 的说明是这样的

计算机科学中,闭包Closure)是词法闭包Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

posted on 2012-11-28 22:55  冠军  阅读(2277)  评论(11编辑  收藏  举报