JavaScript 中的闭包 GenAI LIVE!| 2025 年 6 月 4 日

2025-06-08

JavaScript 中的闭包

GenAI LIVE! | 2025年6月4日

什么是闭包?

我认为 JavaScript 中的闭包是一个高级主题。它是面试中经常被问到的话题之一。
如果你读过我之前的博客文章,或者了解 JavaScript 的作用域,理解闭包会更容易。

JavaScript 中的函数作用域是指在函数内部声明的变量只能在该函数内访问。然而,该函数及其任何子函数都可以访问它。闭包则更进一步。闭包确保即使父函数执行完毕,子函数仍然可以访问父函数的作用域。

例子

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        console.log(`${outerVariable} ${innerVariable}`); // outer inner
    }
    inner();
}

outer();

我创建并执行了outer上面的函数。此操作创建并调用了inner该函数。inner该函数成功记录了它声明的变量以及父函数中的变量。这是预料之中的,因为子函数可以访问父函数的作用域。

现在我们不要调用该inner函数而是返回它。

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        return (`${outerVariable} ${innerVariable}`);
    }
    return inner;
}

const returnFunction = outer();
console.log(returnFunction); // Function returnFunction

变量returnFunction是一个函数,因为它就是 的返回值outer。不出所料。

🚨至此,outer函数执行完成,返回值被赋值给一个新变量

这很关键。JavaScript 垃圾回收机制应该清除所有 的痕迹,outerVariable因为当函数从堆栈弹出并执行完毕时,就会发生这种情况。让我们运行returnFunction

function outer() {
    const outerVariable = "outer";
    function inner() {
        const innerVariable = "inner";
        return (`${outerVariable} ${innerVariable}`); // outer inner
    }
    return inner;
}

const returnFunction = outer();
console.log(returnFunction); // Function returnFunction
console.log(returnFunction()); // outer inner

惊喜!它仍然可以在其父函数(已执行完毕)中记录变量的值。

如果函数返回了子函数,JavaScript 垃圾回收器不会清除该函数的变量。这些子函数可以稍后运行,并且完全有资格根据词法作用域原则访问父函数的作用域。

这种垃圾回收行为并不仅限于子函数。只要还有对象保持对某个变量的引用,该变量就不会被垃圾回收。

现实世界的例子

假设我正在编写一辆汽车的程序。这辆车可以像现实世界中的汽车一样加速,并且每当它加速时,车速就会增加。

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(car.accelerate()); // 3
console.log(car.accelerate()); // 4

您可以看到汽车的速度是如何由 提供的,carMonitor,并且可以通过accelerate函数访问。每次我调用 时accelerate,它不仅可以访问该变量,还可以从上一个值增加并返回该值。

使用闭包创建私有变量

让我们举个例子carMonitor.

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(car.accelerate()); // 3
console.log(car.accelerate()); // 4
console.log(speed); // speed is not defined

您可以看到,变量 是函数 的私有变量carMonitor,并且只能由子函数访问accelerate.。外部任何变量​​都无法访问它。有人可能会说,由于函数作用域的原因,这是理所当然的。它对 是私有的carMonitor,并且对 的每个新实例也是私有的carMonitor

每个实例都维护其副本并增加它!

这应该可以帮助您认识到闭包的威力!

function carMonitor() {
    var speed = 0;

    return {
        accelerate: function () {
            return speed++;
        }
    }
}

var car = new carMonitor();
var redCar = new carMonitor()
console.log(car.accelerate()); // 0
console.log(car.accelerate()); // 1
console.log(redCar.accelerate()); // 0
console.log(redCar.accelerate()); // 1
console.log(car.accelerate()); // 2
console.log(redCar.accelerate()); // 2
console.log(speed); // speed is not defined

carredCar维护自己的私有speed变量,speed外部不可访问。

我们强制使用者使用函数或类上定义的方法,而不是直接访问属性(他们不应该这样做)。这就是你封装代码的方式。

我希望这篇文章能够消除您对 JavaScript 中闭包的任何疑虑!

常见面试问题

这是一个关于闭包的面试问题,经常被问到。

您认为以下代码段的输出是什么:

for (var i = 0; i <= 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

如果你猜的是 0 到 5 之间的数字,中间间隔一秒,那你肯定会大吃一惊。setTimeout 调用时,1 秒后i的值变成了 6!我们希望使用创建时的值i,IIFE + 闭包可以帮助你做到这一点。

for (var i = 0; i <= 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i);
        }, 1000);
    })(i);
}

还有另一种方法可以解决这个问题。使用let关键字。

var在我们用于声明的循环中,i创建了一个函数作用域。这会导致所有循环迭代共享一个绑定。当六个计时器完成时,它们都使用同一个变量,最终值为 6。

let具有块作用域,在for循环中使用时会为循环的每次迭代创建一个新的绑定。循环中的每个计时器都会获得一个不同的变量,其值从 0 到 5 不等。

for (let i = 0; i <= 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

现在输出将是 0 到 5 之间的数字。如果您的目标是 ES5,请使用 IIFE 加闭包方法。如果您可以使用 ES6,请使用let关键字方法。

这就是 Babel 在将 ES6 代码转译为 ES5 时所做的事情。它将上述let代码转换为闭包 + IIFE 的组合!

鏂囩珷鏉ユ簮锛�https://dev.to/bhagatparwinder/closures-in-javascript-1f6k
PREV
那么,“HTML-CSS-JS”和“CSS-in-JS”到底是什么?
NEXT
Async/Await:简介