网站首页 > 教程文章 正文
每个 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 脚本等专栏,案例驱动实战学习,点击原文了解更多详情。
猜你喜欢
- 2025-09-03 一文带你彻底搞懂Proxy和Reflect!
- 2025-09-03 前端最新面试题及答案 (2025)_2021前端面试大全
- 2025-09-03 bind、call、apply 区别?如何实现一个bind?
- 2025-09-03 黑马程序员前端视频-黑马前端教程
- 2025-09-03 Rust从入门到放弃(一):数据类型_rust csdn
- 2025-09-03 仓颉编程学习-变量_仓颉 编程语言
- 2025-09-03 LeetCode.两数之和和三数之和_两数之和 三数之和
- 2025-09-03 javascript中的模块系统_js模块类型
- 2025-09-03 JavaScript面试题精选:10个高频问题详解
- 2025-09-03 第15天|16天搞定前端,javascript语法篇(干货)
- 最近发表
- 标签列表
-
- location.href (44)
- document.ready (36)
- git checkout -b (34)
- 跃点数 (35)
- 阿里云镜像地址 (33)
- qt qmessagebox (36)
- mybatis plus page (35)
- vue @scroll (38)
- 堆栈区别 (33)
- 什么是容器 (33)
- sha1 md5 (33)
- navicat导出数据 (34)
- 阿里云acp考试 (33)
- 阿里云 nacos (34)
- redhat官网下载镜像 (36)
- srs服务器 (33)
- pico开发者 (33)
- https的端口号 (34)
- vscode更改主题 (35)
- 阿里云资源池 (34)
- os.path.join (33)
- redis aof rdb 区别 (33)
- 302跳转 (33)
- http method (35)
- js array splice (33)