从内存视角解析JavaScript闭包的形成机制

作者:小括号

时间:2026-10-27

浏览:93

本文从内存管理的角度深入解析JavaScript闭包的产生过程。通过调用栈与堆内存的交互机制,说明局部变量如何在函数执行后保留。重点分析闭包对象的创建时机、共享策略及内存占用问题,帮助开发者理解闭包对性能的影响及正确的内存释放方式。

文章目录

本次分析将聚焦于内存管理机制,以此阐释闭包产生的底层原理。

函数调用时会生成临时的调用栈,用于保存执行上下文,这些上下文实际存储在栈内存中。

每次函数执行时,其内部的局部临时变量会被暂存。若该函数内部又调用了其他函数,被调用函数的局部变量也会随之保存,层层叠加从而形成调用栈结构。

函数执行结束后,其对应的局部临时变量通常会被销毁。

保存局部变量的核心目的是保护当前的执行上下文现场。

以下通过具体示例进行说明。

function a() {
  const a_num = 99;
  const a_obj = { val: "a" };
  b();
}

function b() {
  const b_str = "text";
  c();
}

function c() {
  const c_bool = true;
  // debugger
}

a();

当嵌套调用 a、b、c 三个函数时,会形成如下所示的调用栈结构。

闭包的产生

基本类型的临时变量直接存储于栈内存中;而对于引用类型,系统会在堆内存中创建对象,并将该对象的内存地址存入栈内存。

引用类型不直接存入栈内存的原因在于栈空间有限,极易发生栈溢出,而引用类型的数据体积通常较大。

闭包的产生

函数执行完毕后,其内部声明的临时变量理论上应被销毁。然而,若存在闭包机制,这些临时变量可得以保留而不被销毁。

具体示例如下。

function createCounter() {
  let count = 0;
  let otherVal = "other val";
  return function counter() {
    // debugger;
    console.log(count++);
  };
}

const counter = createCounter();

console.log(counter());

其执行过程分析如下。

  • 执行 createCounter 函数时,系统会创建一个空的上下文对象。
  • 当检测到内部函数 counter 时,引擎会预扫描该内部函数引用了外部函数 createCounter 中的哪些变量,最终确定 count 变量。随后在堆内存中创建一个闭包 Closure (createCounter) 对象,并将 count 变量存入其中。由于 otherVal 未被内部函数使用,因此不会加入闭包对象。
  • 该内部函数最终被返回并被外部引用,只要引用存在,闭包对象便不会销毁。

通过开发者工具(DevTool)可以观察到该闭包对象的具体状态。

闭包的产生

因此,若返回的闭包函数不再使用,应将其设置为 null。否则,关联的闭包对象将长期占用内存资源。

多个内部函数共享一个闭包对象

此外,若存在多个内部函数,它们将共用同一个闭包对象。即使某个内部函数未被返回,它仍可能向闭包对象中添加数据。

例如,新增一个未返回的 printOtherVal 内部函数,这会导致返回的 counter 函数对应的闭包对象中包含其不需要的 otherVal 变量。

闭包的产生

这是 JavaScript 引擎处理闭包时的策略所致,从理论设计来看,这种效果略显异常。

总结

函数调用时会产生调用栈,将当前函数上下文入栈并保存基本类型变量;引用变量则在堆内存中创建,并在栈内存中保存其引用地址。

由于 JavaScript 中函数是一等公民,因此存在闭包概念。当检测到内部函数时,引擎会创建闭包对象,将内部函数使用到的外部函数变量保存至该对象中。后续调用内部函数时,变量将从闭包中提取,若未找到则从全局上下文提取。

至此,关于 JavaScript 闭包产生机制的详解已完成。如需了解更多相关内容,建议查阅本站过往文章或继续浏览后续介绍,感谢大家的支持。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章