Skip to content

🔐 闭包 (Closure)

闭包是一个持久化在“堆内存”中的实体,它由一个“函数”以及它在创建时所关联的“词法环境”共同组成。它使得函数可以访问那些由于函数执行上下文已销毁而本该被垃圾回收的变量。

词法环境🔬

JavaScript 采用词法作用域(静态作用域)。变量的作用域在代码书写时就已经确定🖊️。

  1. [[Environment]] 属性

    每个函数在诞生(定义)时,都会背上一个隐藏的“小书包”——[[Environment]] 属性🎒。它记录了函数出生那一刻,周围环境里所有的变量及其父级环境。

  2. 环境记录 vs 外部引用

    当函数在别处执行时,它会优先查阅自己的环境记录📋。找不到时,通过“外部引用”跳到出生时的环境查找🔎。

执行上下文⏳

这是闭包最容易被误解的地方:闭包保存的不是“执行上下文”,而是“词法环境”。

  • 上下文的消亡

    当外部函数(如 createCounter)运行结束时,它的执行上下文会立即从**调用栈(Stack)**中弹出并销毁🗑️。

  • 环境的幸存

    虽然“动作”(上下文)结束了,但内部函数仍然通过 [[Environment]] 引用着外部函数的**“数据仓库”(词法环境)**:databases:。引擎为了保证数据的可达性,会阻止该环境被销毁🛡️。

内存管理📦

存储位置正常情况 (非闭包变量)闭包情况 (被引用的变量)
栈 (Stack)函数执行时分配,执行完立即弹出销毁。📤仅存放基本数据类型和引用地址,随调随走。⏩
堆 (Heap)存储复杂对象,由垃圾回收(GC)管理。♻️闭包变量被“提升”至此⬆️。即便函数跑完,只要引用还在,它就永生:immortal:。

🤔 为什么不能留在栈里?

栈必须遵循“后进先出”的严格秩序:queue:。如果闭包变量留在栈里不释放,会导致后续的所有函数调用都被堵死,最终引发 Stack Overflow💥。因此,引擎必须把闭包变量挪到空间更广阔、管理更灵活的里📂。

闭包的应用🧰

核心应用

  • 私有变量封装🔒:模拟类属性,外部无法直接修改 _hp,只能通过 attack() 方法。
  • 状态持久化💾:在异步操作(如 setTimeoutPromise)中,确保回调函数执行时仍能拿到当年的变量。
  • 柯里化与偏函数⚙️:预设部分参数,定制化生成新函数。

闭包的“代价”:内存泄漏:leak:

由于闭包变量长驻堆内存,如果大量创建闭包且不及时销毁,会导致浏览器内存占用飙升📈,甚至卡死💀。