Javascript中的暗物质:闭包

本文深入探讨JavaScript中的闭包概念,解释其工作原理,并通过实例展示如何利用闭包实现数据保护,解决常见问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法 认为闭包是由函数和与其相关的引用环境组合而成的实体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值