LOADING

加载过慢请开启缓存 浏览器默认开启

JavaScript

最近在复习 JavaScript 基础时,重新看了一遍闭包。这个概念一开始会觉得有点抽象,但只要结合函数作用域和实际例子来看,其实并不难理解。

一、什么是闭包

简单来说,闭包就是:函数可以记住并访问它定义时所在的作用域,即使这个函数在外部被调用,依然可以使用原来的变量。

换句话说,函数执行结束后,正常情况下它内部的变量应该被释放;但如果有一个内部函数仍然引用这些变量,那么这些变量就会继续保留下来,这种现象就是闭包。

二、先看一个例子

function outer() {
  let count = 0;

  return function inner() {
    count++;
    console.log(count);
  };
}

const fn = outer();

fn(); // 1
fn(); // 2
fn(); // 3

在这个例子里:

  1. outer() 执行时,创建了变量 count
  2. 它返回了内部函数 inner()
  3. 虽然 outer() 已经执行完了,但是 inner() 仍然引用着 count
  4. 所以每次调用 fn(),都还能继续访问并修改这个变量。

这就是一个典型的闭包。

三、为什么闭包很有用

闭包的常见作用,主要有下面几种:

1. 封装私有变量

function createCounter() {
  let num = 0;

  return {
    add() {
      num++;
      return num;
    },
    get() {
      return num;
    }
  };
}

const counter = createCounter();

console.log(counter.add()); // 1
console.log(counter.add()); // 2
console.log(counter.get()); // 2

这里的 num 无法直接从外部修改,只能通过返回的方法访问。这样就起到了“私有变量”的效果。

2. 保存状态

很多时候,我们希望某些数据在多次调用之间能够保留下来,闭包就非常适合这种场景。

比如计数器、缓存、函数工厂,本质上都在利用闭包保存状态。

3. 生成不同配置的函数

function makeAdder(x) {
  return function (y) {
    return x + y;
  };
}

const add5 = makeAdder(5);
const add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

这里 add5add10 都是闭包,它们分别记住了不同的 x

四、闭包使用时要注意什么

闭包很好用,但也不是越多越好。

1. 占用内存

因为闭包会让被引用的变量继续保存在内存中,如果使用不当,可能会造成不必要的内存占用。

所以当某些闭包不再需要时,应该尽量让相关引用释放掉。

2. 循环中的变量问题

在老项目里,经常会看到 var 和闭包一起使用时带来的问题:

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

输出结果通常是:

3
3
3

原因是 var 没有块级作用域,三个回调共享的是同一个 i

更推荐直接使用 let

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

这样输出就是:

0
1
2

五、怎么理解闭包

如果只记一句话,我觉得可以记这个:

闭包不是某一种特殊语法,而是函数和它所能访问的词法作用域共同形成的结果。

所以学习闭包,关键不是死记定义,而是理解两件事:

  1. 函数可以嵌套定义。
  2. 内部函数可以访问外部函数的变量。

当内部函数在外部继续被使用时,闭包自然就产生了。

六、参考资料

如果想继续深入,可以直接阅读 MDN 的说明,写得比较系统:

MDN 闭包文档(可点击跳转)