JavaScript 闭包到底是什么:用计数器例子一次讲清

举报
yd_278701352 发表于 2026/10/31 02:40:39 2026/10/31
【摘要】 学 JavaScript 时,「闭包」大概是被问得最多、也最容易越听越糊涂的概念之一。其实它没有那么玄:一个函数记住了它出生时所在作用域里的变量,哪怕它后来在别的地方执行,这些变量依然能访问——这就是闭包。 先看一个计数器function createCounter() { let count = 0; return function () { count++; return...

学 JavaScript 时,「闭包」大概是被问得最多、也最容易越听越糊涂的概念之一。其实它没有那么玄:一个函数记住了它出生时所在作用域里的变量,哪怕它后来在别的地方执行,这些变量依然能访问——这就是闭包。

先看一个计数器

function createCounter() {
  let count = 0;
  return function () {
    count++;
    return count;
  };
}

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

按直觉,createCounter 执行完之后,局部变量 count 应该随着函数结束被回收。可结果是它不但活着,还能被返回的内部函数持续修改。原因就是内部函数在定义时「带走」了 count 所在的词法作用域——这个被带走的变量集合,就是闭包的核心。

三个关键点

第一,闭包是定义时决定的,不是调用时。 函数能访问哪些变量,看它写在哪里,不看它在哪里被调用。把上面返回的函数传到任何地方执行,它认的还是当初那个 count。

第二,每次调用外层函数,都会产生一份独立的闭包。

const a = createCounter();
const b = createCounter();
a(); a();      // a 的 count 到 2
console.log(b()); // 1,b 有自己独立的 count

a 和 b 各自持有一份互不干扰的 count,这也是闭包能用来做「私有状态」的原因:外面的代码碰不到 count,只能通过返回的函数去改它。

第三,循环里最容易踩坑。

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出 3、3、3,而不是 0、1、2

var 没有块级作用域,三个回调闭包共享同一个 i,等定时器触发时循环早已结束、i 已经是 3。改成 let 后,每轮循环都是一个独立的绑定,输出就变回 0、1、2。这个例子能想通,闭包和作用域基本就过关了。

它有什么实际用处

  • 封装私有变量:像计数器这样,只暴露方法、不暴露数据本身;
  • 函数工厂:按参数批量生成行为略有差别的函数,比如不同折扣率的计算器;
  • 回调与事件处理:回调里记住当时的上下文变量,是前端代码里最常见的闭包身影;
  • 防抖、节流:计时器句柄就是靠闭包保存下来的状态。

一个提醒

闭包会让被引用的变量无法被垃圾回收。如果在闭包里囤积大对象、又长期不释放,就可能造成内存占用持续增长。用完的大数据该置空就置空,别让「记住」变成「包袱」。

一句话总结:闭包 = 函数 + 它定义时所在作用域的变量。把计数器这个例子亲手敲一遍、再改两下(比如让两个计数器互相干扰试试),比背十遍定义都管用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。