LexicalEnviroment 对象和 [[Enviroment]] 之间的关系

Sky*_*pho 3 javascript closures lexical-closures

据说每个代码块都有一个名为 的隐藏对象LexicalEnviroment。该对象包含对外部作用域的引用和一个EnviromentRecord,其中包含有关当前作用域的信息。

另一方面,据说函数能够关闭,这要归功于它们[[Enviroment]]“记住函数的定义位置”的构造。

我很困惑,object 和之间有什么关系LexicalEnviroment[[Enviroment]]?他们是一回事吗?只有函数有[[Enviroment]]构造吗?那他们有LexicalEnviroment对象吗?

Jor*_*jon 5

tl;博士

它们都是Environment Record.

LexicalEnvironment只是执行上下文的一个组件(函数不能有LexicalEnvironment),并且在调用函数时,LexicalEnvironment会为当前上下文创建一个新的,其[[OuterEnv]]字段被设置为 Function[[Environment]]字段。

如果是 Javascript,我想应该是:

function handleInvokeFunction(func) {
    const localEnv = new EnvironmentRecord();
    localEnv.set('[[OuterEnv]]', func['[[Environment]]'])
    calleeContext.lexicalEnvironment = localEnv;
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我不是该主题的专家。我只是想给你一个整体的想法,同时等待真正的专家在这里插话。

环境记录

Environment Records,用于记录(双关语),保存要执行的功能所需的所有信息。例如,对于函数,它们保存变量声明和this值。当然,这过于简化了[src]

环境记录是一种规范类型,用于定义标识符与特定变量和函数的关联。

每次评估此类代码时,都会创建一个新的环境记录来记录由该代码创建的标识符绑定。

关于环境记录的一件有趣的事情是,它们负责允许访问父变量,例如:

// Environment Record "EnvA"
const hello = "world";
if (1) {
    // Environment Record "EnvB"
    console.log(hello);
}

// outputs: world
Run Code Online (Sandbox Code Playgroud)

那是因为它们有一个名为 的字段[[OuterEnv]],它指向父环境。所以在上面的例子中,[[OuterEnv]]“EnvB”的字段被设置为“EnvA” [src]

每个环境记录都有一个[[OuterEnv]]字段,它要么是空的,要么是对外部环境记录的引用。

每次运行时遇到新的代码块时,它都会执行以下步骤[src]

  1. 创建一个新的环境记录。
  2. [[OuterEnv]]将该新环境的字段设置为旧的(当前活动的)环境。
  3. 返回新环境

执行上下文

为了对所有块执行此操作,使用了一个执行上下文堆栈,这几乎就像堆栈跟踪 [src]。所不同的是,代替仅推动坡平进入离开一个函数(像栈跟踪会做),它将只改变在最高条目进入退出 的代码块(如一个如果块)。

执行上下文是一种规范设备,用于跟踪 ECMAScript 实现对代码的运行时评估。

执行上下文堆栈用于跟踪执行上下文。

执行上下文有一个LexicalEnvironment组件。需要跟踪该特定代码块中的变量。

LexicalEnvironment:标识用于解析此执行上下文中的代码所做的标识符引用的环境记录。[源代码]

LexicalEnvironment 一个环境记录,所以它有一个[[OuterEnv]]字段,这是运行时将相应改变的。

LexicalEnvironment不属于函数对象。它只属于一个执行上下文。

运行执行上下文表示的代码运行时,目前在该时刻执行块[SRC]

正在运行的执行上下文始终是此堆栈的顶部元素。

为了扩展上述步骤,当输入一个新的代码块时,这就是实际发生的[src]

  1. 创建一个具有适当[[OuterEnv]]值的新环境记录(与以前的步骤相同)。
  2. 使用新的环境记录作为运行记录。
  3. 评估块内的所有行。
  4. 恢复到以前的环境记录。
  5. 返回结果并退出块。

评论前面的例子,这就是会发生的事情:

// This is Environment Record "EnvA".
// The [[OuterEnv]] field for "EnvA" is null.
// The running context LexicalEnvironment is "EnvA".

const hello = "world";

if (1) {

    // Found new block

    // Create a new Environment Record "EnvB".

    // Set the "EnvB" [[OuterEnv]] field to
    // the running context LexicalEnvironment.
    // In this case, its "EnvA".

    // Change the running context LexicalEnvironment to "EnvB".
    
    // Evaluate all lines in the body using the new 
    // running context LexicalEnvironment.
    // In this case, its "EnvB".
    
    console.log(hello);
    
    // Restore the previous running context LexicalEnvironment.

    // Return the result.
}

// The running context LexicalEnvironment is Environment Record "A".
// Since the inner block restored before returning, it didn't change.
Run Code Online (Sandbox Code Playgroud)

[[环境]]

不过,还没有提到功能。这是不同的,因为函数可以在声明的范围之外执行。

这就是[[Environment]]出现的地方。

[[Environment]]:函数关闭的环境记录。在评估函数的代码时用作外部环境。

当块内有函数时,运行LexicalEnvironment被存储为[[Environment]]函数对象的字段[步骤 35] [步骤 3] [步骤 14]

调用该函数时,[[Environment]]字段被用作[[OuterEnv]][工序10]

这就像函数将它有权访问的所有变量存储在 中[[Environment]],并且在调用时,它可以再次使用[[Environment]].

与普通块的另一个区别是,在这种情况下,不是更改正在运行的执行上下文,而是创建一个新的执行上下文并将其推送到堆栈[步骤 3 中的创建] [步骤 12 中的推送] [步骤 8 中的弹出]

现在,尝试使用简单的代码:

// This is Environment Record "EnvA".
// The [[OuterEnv]] field for "EnvA" is null.
// The running context LexicalEnvironment is "EnvA".

const hello = "world";

// Found a function, store the running context 
// into its [[Environment]] field, and do nothing else.

function foo() {

    // This block runs only after invoking bar().
    
    // Create a new executing context "calleeContext".

    // Create a new Environment Record "EnvB".

    // Set the "EnvB" [[OuterEnv]] field, to the value
    // stored inside [[Environment]]. In this case, its "EnvA".

    // Set the LexicalEnvironment of "calleeContext" to "EnvB".

    // Push "calleeContext" to the execution context stack.
    // That makes "calleeContext" the running execution context.
    
    // Evaluate all lines in the body
    // using "calleeContext" LexicalEnvironment.
    // In this case, its "EnvB".

    // If a function is found here, set its
    // [[Environment]] to "calleeContext" LexicalEnvironment.
    
    console.log(hello); // works because `hello` was in "EnvA"
    
    // Pop "calleeContext" from the execution context stack.
    // "calleeContext" is no longer the running execution context.
    
    // Return the result.
}

const bar = foo;
bar();

// The [[Environment]] of `bar` is still "EnvA".
// The running context LexicalEnvironment is still "EnvA".
Run Code Online (Sandbox Code Playgroud)

由于该示例在声明它的同一环境中调用该函数,因此它实际上并未使用“闭包”,但您可能已经明白了。

综上所述

虽然这两个[[Environment]]LexicalEnvironmentEnvironment Records,他们使用了不同的事情。

[[Environment]]保存LexicalEnvironment函数的声明位置。

LexicalEnvironment 是执行上下文的一个组件,它存储有关该特定代码块中变量的信息。