你不知道的 JavaScript 系列之闭包

    技术2026-02-23  5

    什么是闭包

    闭包是可以读取其他函数内部变量的函数 当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包

    下面我们可以通过一段代码,清晰地展示了闭包:

    function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // 2 这就是闭包的效果

    函数 bar() 的词法作用域能够访问 foo() 的内部作用域,我们将 bar 所引用的函数对象本身当作返回值,在 foo() 执行后,将其返回值赋值给变量 baz 并调用 baz(),实际只是通过不同的标识符引用调用了内部的函数 bar()。此时,bar() 在自己定义的词法作用域以外的地方执行。

    在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间,由于看上去 foo() 的内容不会再被使用,所以很自然地会考虑对其进行回收。

    而闭包的“神奇”之处正是可以阻止这种事情的发生。事实上内部作用域依然存在,因此没有被回收。

    拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够这一直存活,以供 bar() 在之后任何时间进行引用。

    bar() 依然持有对该作用域的引用,而这个引用就叫做闭包 这个函数在定义时的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。

    无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包

    function foo() { var a = 2; function baz() { console.log(a); } bar(baz); } function bar(fn) { fn(); // 闭包 } foo();

    是不是看着有点死板,但其实这就是我们常用的一种闭包形式

    function wait(message) { setTimeout(function timer() { console.log(message); }, 1000) } wait('hello');

    内置的工具函数 setTimeout 持有对一个参数的引用,这个参数也许叫做 fn 或者 func,或者其他类似的名字。引擎会调用这个函数,在例子中就是内部的 timer 函数,而词法作用域在这个过程中保持完整。这就是闭包。

    本质上,无论何时何地,如果将函数当作第一级的值类型到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、ajax请求或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包。

    模块

    还有其他的代码模式利用了闭包的强大威力,模块就是其中最强的一个。

    function CoolModule() { var something = 'cool'; var another = [1, 2, 3]; function doSomething() { console.log(something); } function doAnother() { console.log(another.join()); } return { doSomething: doSomething, doAnother: doAnother }; } var foo = CoolModule(); foo.doSomething(); // cool foo.doAnother(); // 1,2,3

    模块模式需要具备的两个必要条件:

    必须有外部的封闭函数。该函数必须至少被调用一次;封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

    最后

    虽然闭包非常有用,但是不能过度使用。使用闭包时,所有的信息都会存储在内存中,直到 JavaScript 引擎确保这些信息不再使用或页面卸载时,才会清理这些信息。

    Processed: 0.008, SQL: 9