关闭

声明函数时,其声明的上下文中的变量将在其范围内捕获。例如,在下面的代码中,变量 x 绑定到外部作用域中的值,然后在 bar 的上下文中捕获对 x 的引用:

var x = 4; // declaration in outer scope

function `bar()` {
    console.log(x); // outer scope is captured on declaration
}

bar(); // prints 4 to console

样品输出:4

这种捕获范围的概念很有意思,因为即使在外部范围退出之后,我们也可以使用和修改外部范围中的变量。例如,请考虑以下事项:

function `foo()` {
    var x = 4; // declaration in outer scope

    function `bar()` {
        console.log(x); // outer scope is captured on declaration
    }

    return bar;
    
    // x goes out of scope after foo returns
}

var barWithX = foo();
barWithX(); // we can still access x

样品输出:4

在上面的例子中,当调用 foo 时,它的上下文被捕获在函数 bar 中。所以即使它返回后,bar 仍然可以访问和修改变量 x。函数 foo,其上下文在另一个函数中捕获,被称为闭包

私人数据

这让我们做了一些有趣的事情,比如定义仅对特定函数或函数集可见的私有变量。一个人为的(但很受欢迎的)例子:

function `makeCounter()` {
    var counter = 0;

    return {
        value: function () {
            return counter;
        },
        increment: function () {
            counter++;
        }
    };
}

var a = makeCounter();
var b = makeCounter();

a.increment();

console.log(`a.value()`);
console.log(`b.value()`);

样本输出:

1
0

调用 makeCounter() 时,将保存该函数上下文的快照。makeCounter() 中的所有代码都将在执行时使用该快照。因此,makeCounter() 的两个调用将创建两个不同的快照,其中包含自己的 counter 副本。

立即调用的函数表达式(IIFE)

闭包还通常通过使用立即调用的函数表达式来防止全局命名空间污染。

立即调用的函数表达式 (或者,更直观地说,是自执行的匿名函数 )本质上是在声明之后立即调用的闭包。IIFE 的一般想法是调用创建单独上下文的副作用,该上下文只能由 IIFE 中的代码访问。

假设我们希望能够用 $ 引用 jQuery。考虑一下天真的方法,不使用 IIFE:

var $ = jQuery;
// we've just polluted the global namespace by assigning window.$ to jQuery

在下面的示例中,IIFE 用于确保 $ 仅在闭包创建的上下文中绑定到 jQuery

(function ($) {
    // $ is assigned to jQuery here
})(jQuery);
// but window.$ binding doesn't exist, so no pollution

有关闭包的更多信息,请参阅 Stackoverflow 上的规范答案