跳到主要内容

闭包产生的原因是什么?

面试速答(30 秒版 TL;DR)

  • 闭包(closure)本质:函数 + 它创建时的词法环境(lexical environment)。
  • 产生原因:JS 是词法作用域,且函数是一等公民(能返回、能传参)。当函数在其定义作用域之外执行时,仍然需要访问外层变量,所以运行时必须“保留那份环境”。
  • 一句话:为了让“脱离定义现场的函数”仍能读写外层变量,JS 用闭包把外层环境引用挂在函数对象上。

心智模型:为什么需要“保留环境”

闭包经常出现在两类场景:

  • 返回函数:外层函数执行完了,但你把内层函数返回出去,后续仍要读写外层变量。
  • 回调/异步:外层函数结束后,回调稍后执行,但回调仍要用到外层变量(定时器、事件、Promise 等)。

关键点:词法作用域决定“去哪里找变量”

JS 的变量解析取决于代码写在哪里(lexical),而不是函数从哪里被调用。于是:

  1. 内层函数引用了外层变量(自由变量 free variable)。
  2. 这个内层函数被传到外层作用域之外执行。
  3. 运行时必须保证:该函数执行时,仍能沿着“定义时的作用域链”找到这些变量。

图示:函数对象持有词法环境的引用

你可以把它讲成:只要还有函数引用着那份环境,GC 就不能回收它(环境被“活着的引用”保留)。


最小例子:外层结束后变量仍可用

function makeCounter() {
let n = 0;
return function inc() {
n += 1;
return n;
};
}

const c = makeCounter();
c(); // 1
c(); // 2

为什么 makeCounter() 早就返回了,n 还在?

  • inc 需要 n,所以 inc 必须持有 n 所在的那份词法环境引用,这就是闭包。

常见追问

Q1:闭包“保存的是值”还是“引用”?

一般面试口径:保存的是对变量所在环境的引用,所以后续修改能反映出来。

function f() {
let x = 1;
const g = () => x;
x = 2;
return g;
}
f()(); // 2

Q2:闭包一定会导致内存泄漏吗?

不一定。闭包只是“延长生命周期”,是否泄漏取决于:

  • 你是否长期保留了不必要的引用(例如把回调挂到全局、或事件监听不解绑)。
  • 是否闭包捕获了“大对象/DOM/缓存”导致占用长期不释放。

易错点/坑

  • “只有 return 内层函数才算闭包”是误解:只要函数使用了外层自由变量,它就是闭包(即使没 return,只是作为回调传出去)。
  • 闭包是语言机制,不是“黑魔法”;真正要注意的是生命周期和引用管理

速记要点(可背诵)

  • 闭包 = 函数 + 定义时词法环境。
  • 原因:词法作用域 + 函数可被传递/返回,导致函数可能在定义域之外执行,运行时必须保留外层环境。