JavaScript 中的闭包到底是什么?

您可能在 JavaScript 开发或教程中听说过"闭包"这个术语,但它到底是什么呢?

什么是闭包?

用最简单的术语来说,闭包是一个函数 (严谨的说,闭包不是函数,所有函数都与其周围的作用域形成闭包,这里是为了简单说明而如此举例) ,它会记住定义它的变量,而不管它稍后在哪里执行。让我们用一个例子来分解它:

JavaScript 复制代码
function outerFunction() {
    let outerVariable = 'I am outside!';

    function innerFunction() {
        console.log(outerVariable);
    }

    return innerFunction;
}

const myInnerFunction = outerFunction();
myInnerFunction(); // Outputs: 'I am outside!'

在此示例中,innerFunction 是一个闭包。为什么?因为它是在另一个函数 (outerFunction) 中定义的函数,并且即使在outerFunction 执行完毕后,它也可以访问outerFunction 作用域的变量(在本例中为outerVariable)。

当outerFunction被调用时,它定义outerVariable,定义innerFunction,然后返回innerFunction。即使outerFunction已经完成执行并且outerVariable理论上会超出范围并被垃圾收集,innerFunction仍然可以访问outerVariable。

闭包有什么用?

数据封装:

您可以使用闭包来创建私有变量和方法。这对于代码的模块化设计特别有用。

JavaScript 复制代码
function createCounter() {
    let count = 0;
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
    };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.decrement()); // 1

在此示例中,count 充当私有变量。它只能由递增和递减函数访问,而不能从外部访问。

维持状态

在 JavaScript 中,闭包通常用于维护异步操作、事件处理程序等中的状态。

JavaScript 复制代码
function setupAlertTimeout() {
    var message = 'Message after 3 seconds';
    setTimeout(function alertMessage() {
        alert(message);
    }, 3000);
}

setupAlertTimeout();

在这里,即使在 setupAlertTimeout 执行完毕后,alertMessage 函数仍保持对消息变量的访问。

柯里化

闭包允许使用柯里化,这是一种将具有多个参数的函数计算为具有单个参数的函数序列的技术。

JavaScript 复制代码
function multiply(a) {
    return function(b) {
        return a * b;
    };
}

const multiplyByTwo = multiply(2);
console.log(multiplyByTwo(4)); // 8

在本例中,multiplyByTwo 是一个记住 a 值(即 2)的闭包。

试试看

考虑以下代码片段:

ini 复制代码
function outer() {
    var count = 0;
    return function inner() {
        count++;
        return count;
    };
}
var counter1 = outer();
var counter2 = outer();
console.log(counter1());
console.log(counter1());
console.log(counter2());
console.log(counter2());

上述代码的输出是什么?为什么?

解决方案

在提供的代码中,通过两次调用 external() 创建两个单独的闭包。每个闭包都维护自己单独的词法环境,其中 count 变量在调用之间保留。以下是逐步发生的情况:

  1. var counter1 = 外部(); 创建第一个闭包。在这个闭包内,count 被初始化为 0。

  2. counter1() 第一次被调用。这会将第一个闭包内的 count 增加到 1 并返回 1。

  3. counter1() 再次被调用。这会将第一个闭包内的 count 增加到 2 并返回 2。

  4. var counter2 = 外部(); 创建第二个闭包。在这个闭包内,一个新的、单独的计数被初始化为 0。

  5. counter2() 第一次被调用。这会将第二个闭包中的计数增加到 1 并返回 1。

  6. counter2() 再次被调用。这会将第二个闭包中的计数增加到 2 并返回 2。因此,代码的输出将是:

    1
    2
    1
    2

一个实际的例子

scss 复制代码
function showDelayedMessage(message, delay) {
    setTimeout(function() {
        console.log(message);
    }, delay);
}

showDelayedMessage('Hello after 3 seconds', 3000);
showDelayedMessage('Hello after 5 seconds', 5000);

在不利用闭包的情况下编写像 showDelayedMessage 这样的函数通常会涉及使用全局变量

javascript 复制代码
var globalMessage; // Declare a global variable

function showDelayedMessageGlobal(message, delay) {
    globalMessage = message; // Assign the message to the global variable
    setTimeout(function() {
        console.log(globalMessage);
        globalMessage = null; // Clear the message after displaying it
    }, delay);
}

showDelayedMessageGlobal('Hello after 3 seconds', 3000);

这个示例说明,虽然您可以避免闭包,但这样做通常会导致不太理想的编码实践,例如全局名称空间污染,尤其是在处理异步事件或封装功能时。闭包提供了一种用函数封装数据的干净而有效的方法,这就是为什么尽管有其他方法可用,但闭包仍被如此广泛使用的原因。

相关推荐
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端
爱敲代码的小鱼10 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax