JavaScript 核心约 6 分钟

闭包不是玄学:从作用域链看内存里留下的是什么

闭包常被解释成「函数记住了外部变量」,但更关键的是它为什么会被保留、什么时候会被释放,以及它在真实代码里的两种常见用法。

显示器上展示 JavaScript 函数与作用域相关代码
本文目录

函数在定义时就确定了它能访问哪些变量,这条链由定义位置决定,而不是调用位置。闭包就是函数带着这条链一起被保存下来。

闭包是函数记住了外层变量

JavaScript
function createCounter() {
  let count = 0;

  return {
    increment() {
      count += 1;
      return count;
    },
    current() {
      return count;
    },
  };
}

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

createCounter 执行完后,它的局部作用域按理应该被回收,但因为返回的两个函数还引用着 count,这个变量被保留了下来。count 变成了外部无法直接修改、只能通过方法访问的状态。

什么时候会意外留住内存

闭包保留的是变量本身,只要闭包还被引用,变量就不会释放。定时器、事件监听、缓存对象里保存了闭包却没清理,就会让本该释放的数据一直占着内存。

  • 定时器回调引用了大对象,清理时记得 clearInterval
  • 事件监听在组件卸载时没有移除
  • 用闭包做缓存时没有设置上限

在真实代码里的两种用法

  • 封装私有状态:模块内部可写,外部只能通过暴露的方法读写
  • 保存配置与上下文:事件回调需要用到注册时的变量,而不是触发时的变量
闭包不是需要刻意制造的东西,它只是作用域规则自然产生的结果。

相关阅读