返回
闭包深度解析:从作用域链和内存视角
前端
2023-10-31 08:52:52
对于闭包,网上的文章往往泛泛而谈,仅提及闭包可访问自由变量,而解释其缘由时,常常以“子函数作用域引用未释放”搪塞。实则,闭包的存在基于更深刻的原理。
本篇文章将从作用域链和内存角度深入剖析闭包,揭开其神秘面纱。
作用域链:闭包的寻根
在 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 编程能力。