您是什么人,JavaScript中的闭包?

在本文中,我将尝试详细分析在JavaScript中实现闭包的机制。 为此,我将使用Chrome浏览器。

让我们从定义开始:
闭包是引用独立(自由)变量的函数。 换句话说,闭包中定义的功能“记住”了创建它的环境。
MDN

如果您在此定义中不清楚某件事,那就不可怕了。 继续阅读。

我深信通过特定的示例可以更轻松,更快速地理解某些内容。

因此,我建议您采取一段代码,并与解释器一起从头到尾逐步执行,并整理正在发生的事情。

因此,让我们开始吧:


图1

我们处于调用的全局上下文中,它是Global(在浏览器中又称为Window),我们看到主要功能已经在当前上下文中并且可以使用了。


图2

发生这种情况是因为所有功能声明(以下称为FD)始终在任何上下文中都出现,立即初始化并准备工作。 通过var声明的变量也会发生同样的事情,只有变量的值被初始化为undefined。

同样重要的是要了解JavaScript还会“提高”通过let和const声明的变量。 唯一的区别是它不会将它们初始化为var或FD。 因此,当我们尝试在初始化之前访问它们时,会出现参考错误。

另外,在main中,我们看到一个内部隐藏的属性[[Scopes]] -这是main可以访问的外部上下文的列表。 在我们的案例中,Global在那里,因为main是在全球范围内启动的。

在JavaScript中,对外部环境的引用的初始化是在创建函数时而不是在执行时发生的,这表明JS是一种具有静态作用域的语言。 这很重要。

继续:


图3

我们进入主要功能,首先引起您注意的是Local对象(在规范中-localEnv)。 在这里,我们看到一个 ,因为该变量是通过var声明的,并且会“弹出”,按照传统,我们会看到所有3个FD(foo,bar,baz)。 现在,让我们弄清楚它们的来源。

当任何上下文启动时,将启动抽象操作NewDeclarativeEnvironment ,该操作允许您初始化LexicalEnvironment (以下简称LE)和VariableEnvironment 。 另外, NewDeclarativeEnvironment接受1个参数-外部LE,以创建我们上面讨论的[[Scopes]]。 LE是一种API,可让我们定义标识符与各个变量,函数之间的关系。 LE由2个组件组成:

  1. 记录环境 -一种环境记录,可让您确定标识符与当前呼叫上下文中我们可以使用的内容之间的关系
  2. 链接到外部LE。 每个函数在创建时都有一个内部[[Scopes]]属性

VariableEnvironment-通常与LE相同。 两者之间的区别在于VariableEnvironment的值从不更改,而LE在代码执行期间可以更改。 为了简化进一步的理解,我建议将这些组件组合为一个-LE。

同样,在当前的Local中也存在此问题,这是因为调用ThisBinding的事实-这也是在当前上下文中初始化此方法的抽象方法。

当然,每个FD都会立即收到[[范围]]:


图4

我们看到在[[Scopes]]中收到的所有FD都是[Closure main,Global]数组,这是合乎逻辑的。

同样在图中,我们看到了调用栈 -这是一种按照LIFO原理工作的数据结构-后进先出。 由于JavaScript是单线程的,因此一次只能执行一个上下文。 在我们的案例中,这是主要功能的上下文。 每个新的函数调用都会创建一个新的上下文,并将其堆叠。

堆栈的顶部始终是当前执行上下文。 函数完成执行并解释器退出后,将调用上下文从堆栈中删除。 这就是我们在本文中需要了解的有关调用堆栈的全部信息:)

我们总结了当前情况下发生的情况:

  • 在创建时,主要收到[[Scopes]]并带有指向外部环境的链接
  • 口译员输入了主要功能的正文
  • 调用堆栈获得了执行上下文主体
  • 这样初始化
  • 初始化LE

实际上,最困难的部分已经结束。 我们继续执行代码中的下一步:

现在我们需要调用baz以获得结果。


图5

新的baz调用上下文已添加到“调用堆栈”。 我们看到一个新的Closure对象已经出现。 在这里,我们可以从[[Scopes]]中获得可用的东西。 所以我们到了重点。 这是关闭。 如图4所示, closure(主要)在baz的“ backup”上下文列表中排在第一位。 再次没有魔术。

我们叫foo:


图6

重要的是要知道,无论我们在哪里叫foo,它都将始终遵循其[[Scopes]]链中未定义的标识符。 即,在main中然后在Global中(如果未在main中找到)。

执行foo之后,她返回该值,并且上下文从“调用堆栈”中跳出。
我们传递给bar函数的调用。 在执行bar的上下文中,存在一个变量,其名称与LE foo- a中的变量相同。 但是,正如您已经猜到的,这不会影响任何事情。 foo仍将从[[Scopes]]中获取值。
调用位置不影响作用域,仅影响创建位置
洛加乔娃


图7

结果,baz将返回300,并将其从调用堆栈中抛出。 然后,在主上下文中也会发生同样的事情,我们的代码片段将完成执行。

我们总结:

  • 在函数创建期间,设置了[[Scopes]] 。 这对于理解闭包非常重要,因为解释器在搜索值时会立即跟随这些链接
  • 然后,当调用此函数时,将创建一个活动的执行上下文,并将其放置在调用堆栈中
  • 执行thisBinding并将其设置为当前上下文
  • LE已初始化,并且所有函数参数,通过var和FD声明的变量均可用。 此外,如果有通过let或const声明的变量,它们也将添加到LE中
  • 如果解释器在当前上下文中未找到任何标识符,则使用[[Scopes]]进行进一步搜索,这些搜索又依次进行排序。 如果找到该值,则指向该值的链接将进入特殊的Closure对象。 同时,对于当前关闭的每个上下文,将使用必要的变量创建一个单独的Closure
  • 如果在任何范围(包括全局)中都找不到该值,则返回ReferenceError。

仅此而已!

我希望本文对您有所帮助,现在您了解JavaScript中的锁定机制是如何工作的。

再见:)很快再见。 喜欢并订阅我的频道:)

Source: https://habr.com/ru/post/zh-CN459166/


All Articles