闭包

132 阅读3分钟

词法环境

在 JavaScript 中,每个运行的函数,代码块 {...} 以及整个脚本,都有一个被称为 词法环境(Lexical Environment) 的内部(隐藏)的关联对象。

词法环境对象由两部分组成:

  1. ** 环境记录(Environment Record) ** —— 一个存储所有局部变量作为其属性(包括一些其他信息,例如 this 的值)的对象。
  2. 对 外部词法环境 的引用,与外部代码相关联。

Step 1. 变量

现在看起来都挺简单的,是吧?

  • 变量是特殊内部对象的属性,与当前正在执行的(代码)块/函数/脚本有关。
  • 操作变量实际上是操作该对象的属性。

Step 2. 函数声明

一个函数其实也是一个值,就像变量一样。

*** 不同之处在于函数声明的初始化会被立即完成。*** 当创建了一个词法环境(Lexical Environment)时,函数声明会立即变为即用型函数(不像 let 那样直到声明处才可用)。
这就是为什么我们可以在(函数声明)的定义之前调用函数声明。

Step 3. 内部和外部的词法环境

在一个函数运行时,在调用刚开始时,会自动创建一个新的词法环境以存储这个调用的局部变量和参数。

Step 4. 返回函数

*** 在变量所在的词法环境中更新变量。 ***

闭包

开发者通常应该都知道“闭包”这个通用的编程术语。

闭包 是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 "new Function" 语法 中讲到)。

也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。

在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节

垃圾收集

通常,函数调用完成后,会将词法环境和其中的所有变量从内存中删除。因为现在没有任何对它们的引用了。与 JavaScript 中的任何其他对象一样,词法环境仅在可达时才会被保留在内存中。

……但是,如果有一个嵌套函数在函数结束后仍可达,则它具有引用词法环境的 [[Environment]] 属性。

在下面这个例子中,即使在函数执行完成后,词法环境仍然可达。因此,此嵌套函数仍然有效。