云计算、AI、云原生、大数据等一站式技术学习平台

网站首页 > 教程文章 正文

我以为自己懂闭包,直到遇见了它的真面目

jxf315 2025-09-03 00:44:44 教程文章 8 ℃

每个 JavaScript 开发者都认为自己理解闭包,直到他们发现其实并非如此。

闭包是个看似简单,但一不小心就会出现意外行为的概念。

我自认为已经掌握闭包,结果碰到了意想不到的问题。

接下来,我们一起好好剖析一下闭包。


闭包到底是什么?

简单来说,闭包是函数与其声明时所处的词法环境(也就是外部作用域变量的引用)的结合体。

换句话说,闭包让函数可以访问其外层作用域中的变量,即使外层作用域已经执行结束。

闭包 = 函数 + 它所“保留”的词法作用域。

听起来很直观吧?

来看个经典例子:

function outerFunction() {
let count = 0;
function innerFunction() {
console.log(count);
}
return innerFunction;
}

const fn = outerFunction();
fn(); // 0

尽管 outerFunction() 已经执行完毕并返回, innerFunction() 仍能访问 count 。这就是闭包的魔力。


复杂部分:循环里的闭包陷阱

如果闭包这么简单,大家不会头疼。

问题出在循环里。

假设你想在循环中创建函数数组,每个函数打印它在循环中的索引。

你猜会输出什么?

const funcs = [];
for (var i = 0; i < 3; i++) {
funcs.push(() => console.log(i));
}
funcs[0](); // ?
funcs[1](); // ?
funcs[2](); // ?

如果你猜是 0 1 2 ,恭喜你,错了。

实际输出是:

3
3
3

这是什么情况?


原因和解决方案

问题在于:

var i 是函数作用域,不是块级作用域。当函数执行时,循环已经结束, i 的值变成了3。

解决方案一:用 let 替代 var

let 是块级作用域,每次循环都会捕获一个新的 i

const funcs = [];
for (let i = 0; i < 3; i++) {
funcs.push(() => console.log(i));
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2

这样问题迎刃而解。

解决方案二:使用立即执行函数表达式(IIFE)

如果只能用 var ,用 IIFE 创建单独作用域:

const funcs = [];

for (var i = 0; i < 3; i++) {
(function(index) {
funcs.push(() => console.log(index));
})(i);
}

funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2

每个 IIFE 都“冻结”了当时的 i 值,保证正确输出。


闭包在实际开发中的用武之地

闭包不仅是理论,更是日常开发中不可或缺的工具。

1. 数据隐私(封装)

想要创建私有变量?闭包帮你实现。

function createCounter() {
let count = 0;
return {
increment: () => count++,
getCount: () => count
};
}

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

count 不可被外部直接访问,实现了私有状态。

2. 记忆状态的事件监听器

事件监听器需要记住状态时,闭包非常实用。

function attachListener(buttonId) {
let clicks = 0;
document.getElementById(buttonId).addEventListener("click", () => {
clicks++;
console.log(`按钮被点击了 ${clicks} 次`);
});
}

attachListener("myButton");

即使 attachListener() 已执行完,事件回调依旧能访问和更新 clicks


常见闭包误区

示例:

function createLeakyFunction() {
let hugeObject = new Array(1000000).fill("");
return () => console.log(hugeObject.length);
}

const leaky = createLeakyFunction();
// hugeObject 仍在内存中

解决办法是及时释放引用:

function createLeakyFunction() {
let hugeObject = new Array(1000000).fill("");
return () => {
console.log(hugeObject.length);
hugeObject = null; // 释放内存
};
}

你真的理解闭包了吗?

闭包是 JavaScript 中最容易被误解的概念之一,但一旦掌握,你会在无数场景发现它的身影。

无论是状态管理、循环问题,还是封装逻辑,闭包都给你强大能力——前提是用得正确。


重点总结


现在,去写更干净、更聪明的 JavaScript 吧!

前端AI·探索:涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏,案例驱动实战学习,点击原文了解更多详情。



最后:
深入React:从基础到最佳实践完整攻略
python 技巧精讲
React Hook 深入浅出
CSS技巧与案例详解
vue2与vue3技巧合集




最近发表
标签列表