资讯动态

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

发布时间:2026/10/14 3:36:37 来源:尧图企业网站定制
JavaScript 闭包是什么为什么变量没有被释放什么时候会内存泄漏闭包是函数与它所处词法环境的组合使函数能够访问定义位置外层的变量。外层函数返回以后只要相关函数及环境仍然可达需要的变量就可能继续存在。闭包不是把当时所有变量都拍成一份快照。多个函数可以共享同一个变量读取时看到后续修改不同工厂调用又可以形成独立状态。这本身是正常能力常用于封装状态和回调。内存泄漏的问题是不再需要的数据仍被长期引用。例如全局事件监听器或计时器保留了回调回调又保留面板数据。因此排查时要看引用链并在生命周期结束时取消监听、计时器和订阅而不是见到闭包就改写。函数返回了为什么还能记住计数先看一个能运行的例子下面是同一个计数器的 TS 和 Python 写法。两者都能说明“返回的函数保留了访问外层状态的能力”但不代表两种语言的内存实现完全相同。TypeScriptfunctionmakeCounter(){letcount0;return(){count1;returncount;};}constamakeCounter();constbmakeCounter();console.log(a(),a(),b());// 1、2、1Pythondefmake_counter():count0defincrease():nonlocalcount count1returncountreturnincrease amake_counter()bmake_counter()print(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 垃圾回收通常能够处理不可达的循环。关键是它是否仍然从根对象可达不是两个对象是否互相指着。把回调注册到按钮上按钮删除了就一定没问题吗要看还有谁引用按钮、回调和相关数据。如果按钮与监听关系整体已不可达可以被回收如果全局注册表仍持有它们就可能继续留下。闭包和全局变量哪个更好闭包可以把状态限制在需要它的函数组中减少全局暴露。但仍然要管理生命周期。封装方式不同不代表自动获得零泄漏保证。面试速记卡闭包函数保留对定义位置相关词法环境的访问。状态同一环境可以共享不同调用可以独立。回收看是否可达不看外层函数是否已经返回。泄漏已经不需要的数据仍被长期入口保留。清理取消监听、计时器和订阅检查真实引用链。我是程序员Sunday。本文收录于 sunday面试指南原文与配图可继续阅读。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑