返回

闭包深度解析:从作用域链和内存视角

前端

对于闭包,网上的文章往往泛泛而谈,仅提及闭包可访问自由变量,而解释其缘由时,常常以“子函数作用域引用未释放”搪塞。实则,闭包的存在基于更深刻的原理。

本篇文章将从作用域链和内存角度深入剖析闭包,揭开其神秘面纱。

作用域链:闭包的寻根

在 JavaScript 中,函数执行时会形成一个执行上下文,其中包含变量对象(Variable Object, VO)和作用域链(Scope Chain)。VO 存储着该函数及父函数的作用域内所有变量,而作用域链则是一条指向所有父函数 VO 的链条。

当闭包被创建时,它将继承其外层函数的作用域链。即使外层函数执行完毕,闭包仍然可以访问外层函数的变量,因为这些变量仍然保存在作用域链中的父级 VO 中。

内存管理:闭包的生命线

闭包的生存离不开 JavaScript 的垃圾回收机制。当一个函数执行完毕后,其 VO 会被垃圾回收器标记为可释放。然而,如果该函数中有闭包引用了其变量,那么这些变量所在的 VO 将不会被释放,因为闭包仍然持有对它们的引用。

这种引用关系形成了一条从闭包到 VO 的路径,阻止了垃圾回收器回收这些变量。这就是为什么闭包可以访问自由变量的原因:它们保住了这些变量所在 VO 的生命。

示例图解:闭包的运作方式

function outer() {
  const a = 1;
  return function inner() {
    console.log(a); // 仍然可以访问自由变量 a
  };
}

const inner = outer(); // 创建闭包

在这个示例中,当 outer 函数执行完毕后,其 VO 会被标记为可释放。然而,由于 inner 闭包引用了 a 变量,a 所在的 VO 便不会被释放。

作用域链如下图所示:

+-----------------------+
| outer 作用域         |
+-----------------------+
| a: 1                  |
+-----------------------+
| inner 作用域         |
+-----------------------+
| [指向 outer VO]     |
+-----------------------+

内存管理示意图如下:

+----------------------+
| a 所在的 VO         |
+----------------------+
| 引用计数:2          |
+----------------------+
| 路径:               |
|   - inner 闭包        |
|   - inner VO         |
+----------------------+

总结

通过分析作用域链和内存管理机制,我们可以深入理解闭包的底层原理:

  • 闭包继承了其外层函数的作用域链,可以访问外层函数的变量。
  • 闭包的存在阻止了垃圾回收器释放其所引用变量的 VO。
  • 因此,闭包可以访问自由变量,即使外层函数已经执行完毕。

掌握这些原理对于理解和编写闭包至关重要,它将极大地提升我们的 JavaScript 编程能力。