Sunday 的面试指南

JavaScript 闭包是什么?为什么变量没有被释放,什么时候会内存泄漏?

🧑‍💻 面试官:闭包是什么?

🙋‍♂️ 我:函数能够访问它外层作用域中的变量。

🧑‍💻 面试官:外层函数已经返回,变量为什么还能访问?

🙋‍♂️ 我:返回的函数仍然保留了对相应环境的引用。

🧑‍💻 面试官:那闭包一定会内存泄漏吗?页面已经关掉了某个面板,面板的数据却还占着内存,你会沿着什么去查?

闭包的重点是「引用还在不在」。函数结束不等于相关数据必须回收,继续可达也不等于一定发生了泄漏。

面试速答(60 秒版)

闭包是函数与它所处词法环境的组合,使函数能够访问定义位置外层的变量。外层函数返回以后,只要相关函数及环境仍然可达,需要的变量就可能继续存在。

闭包不是把当时所有变量都拍成一份快照。多个函数可以共享同一个变量,读取时看到后续修改;不同工厂调用又可以形成独立状态。

这本身是正常能力,常用于封装状态和回调。内存泄漏的问题,是不再需要的数据仍被长期引用。例如全局事件监听器或计时器保留了回调,回调又保留面板数据。

因此,排查时要看引用链,并在生命周期结束时取消监听、计时器和订阅,而不是见到闭包就改写。

闭包:函数仍能访问外层状态

知识点详解:函数返回了,为什么还能记住计数?

先看一个能运行的例子

下面是同一个计数器的 TS 和 Python 写法。两者都能说明“返回的函数保留了访问外层状态的能力”,但不代表两种语言的内存实现完全相同。

function makeCounter() {
  let count = 0;
  return () => {
    count += 1;
    return count;
  };
}

const a = makeCounter();
const b = makeCounter();
console.log(a(), a(), b()); // 1、2、1

以 TS 这段代码为例,调用 makeCounter() 时创建 count 和内部函数。外层返回后,a 仍然指向内部函数,内部函数需要访问那次调用中的 count,所以这份状态仍然能使用。

第二次调用创建了另一份状态,b 不会继承 a 已经增加的数字。MDN 的闭包指南解释了函数与词法环境的关系。

它记住的是变量关系,不是冻结的旧值

如果两个内部函数共同访问同一次调用里的 count,一个增加,另一个读取,就会看到增加后的值。

因此,说“闭包捕获变量”通常比说“复制变量值”更准确。对于具体语言和写法,还要看作用域规则。

常见的循环题也是这样:var 不创建每次迭代独立的块级绑定,多个回调可能读到同一个最后值;用 let 时,每次迭代会有对应绑定,结果就不同。

不要把这种题背成“闭包总能保存当时的值”,因为它恰好考的是多个回调有没有共享同一个变量。

正常保留和泄漏,差在哪里?

计数器还在使用,count 保留下来是必要的状态。没有它,计数功能就不能继续。

但假设页面有一个大型图表面板,创建时注册了 window 上的事件监听器。面板关闭后,监听器没有移除,回调还在,回调所需的图表数据也可能仍在。

从垃圾回收的角度,它仍然可达;从产品的角度,它已经不该继续占用。这才是泄漏要关注的关系。

也不是外层定义过的所有对象都一定被完整保留。引擎可能优化,具体要看实际引用、代码与分析工具,不能靠源码里“在同一作用域”就计算内存占用。

面板关了,引用链可能还在

清理要清掉长期保留的入口

移除监听器时,需要使用对应的事件类型、回调引用和相关选项。重新写一个长得一样的箭头函数,不是原来的回调,通常移除不了。

计时器需要取消,订阅需要退订。只把局部变量设成 null,如果长期入口仍然持有真正的数据或另一个引用,也解决不了问题。

实际排查可以反复打开、关闭面板,观察堆快照中的 retained size 与 retaining path。看是谁仍然保留它,再判断那条引用是否应该存在。

这样得到的是能验证的清理方案,而不是“闭包会泄漏,所以不用闭包”的结论。

面试官继续追问

有循环引用,就一定回收不了吗?

现代 JavaScript 垃圾回收通常能够处理不可达的循环。关键是它是否仍然从根对象可达,不是两个对象是否互相指着。

把回调注册到按钮上,按钮删除了就一定没问题吗?

要看还有谁引用按钮、回调和相关数据。如果按钮与监听关系整体已不可达,可以被回收;如果全局注册表仍持有它们,就可能继续留下。

闭包和全局变量,哪个更好?

闭包可以把状态限制在需要它的函数组中,减少全局暴露。但仍然要管理生命周期。封装方式不同,不代表自动获得零泄漏保证。

面试速记卡

  • 闭包:函数保留对定义位置相关词法环境的访问。
  • 状态:同一环境可以共享,不同调用可以独立。
  • 回收:看是否可达,不看外层函数是否已经返回。
  • 泄漏:已经不需要的数据,仍被长期入口保留。
  • 清理:取消监听、计时器和订阅,检查真实引用链。
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历