Lexical Environment 对象和 [[Environment]] 之间的关系

Relationship between the LexicalEnviroment object and [[Enviroment]]

据说每个代码块都有一个名为LexicalEnviroment的隐藏对象。该对象包含对 outer 作用域的引用和 EnviromentRecord,后者包含有关 current 作用域的信息。

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

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

tl;博士

它们都是 Environment Record.

的实例

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

如果是Javascript,我猜是:

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

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

环境记录

Environment Records, for the record (pun intended), hold all information required for the function to be executed. For functions, for instance, they hold the variable declarations, and the this value. Of course, this is oversimplified [src].

Environment Record is a specification type used to define the association of Identifiers to specific variables and functions.

Each time such code is evaluated, a new Environment Record is created to record the identifier bindings that are created by that code.

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

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

// outputs: world

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

Every Environment Record has an [[OuterEnv]] field, which is either null or a reference to an outer Environment Record.

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

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

执行上下文

为了对所有块执行此操作,使用了一个执行上下文堆栈,这几乎就像一个堆栈跟踪[src]。区别在于 pushingpopingenteringexiting一个函数(就像堆栈跟踪一样),它只会改变 进入退出 代码块(如 if 块)。

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

The execution context stack is used to track execution contexts.

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

LexicalEnvironment: Identifies the Environment Record used to resolve identifier references made by code within this execution context. [src]

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

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

运行 执行上下文表示运行时当前正在执行的代码块 [src].

The running execution context is always the top element of this stack.

为了扩展上述步骤,当输入新的代码块时,实际会发生这种情况 [src]:

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

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

// 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.

[[环境]]

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

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

[[Environment]]: The Environment Record that the function was closed over. Used as the outer environment when evaluating the code of the function.

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

调用该函数时,[[Environment]]字段用作[[OuterEnv]][step 10]

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

再次访问它们

与普通块的另一个区别是,在这种情况下,不是更改 运行 执行上下文,而是创建一个新的并将其推送到堆栈[creation in step 3][push in step 12][pop in step 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".

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

总结

虽然 [[Environment]]LexicalEnvironment 都是 Environment Records,但它们的用途不同。

[[Environment]] 包含声明函数的 LexicalEnvironment

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