词法环境对象与[[Environment]]之间的关系

3
据说每个代码块都有一个隐藏对象叫做LexicalEnviroment。该对象包含对外部作用域的引用和一个EnviromentRecord,其中包含关于当前作用域的信息。
另一方面,函数能够闭包是因为它们具有[[Enviroment]]构造,可以“记住函数定义的位置”。
我很困惑,“LexicalEnviroment对象和[[Enviroment]]之间的关系是什么?”它们是一样的吗?只有函数有[[Enviroment]]构造吗?那它们是否有LexicalEnviroment对象呢?

1
是的,它们是同一个东西。函数对象通过它们的 [[Environment]] 插槽记住了它们被定义的词法环境。 - Bergi
1个回答

5

简而言之

它们都是 Environment Record 的实例。

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

如果这是 JavaScript,我猜应该是:

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

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

环境记录

环境记录,记录(双关语)所有执行所需的信息。例如,在函数中,它们保存变量声明和this值。当然,这是过于简化的[src]

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

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

环境记录的一个有趣之处在于,它们负责允许访问父变量,例如:

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

// outputs: world

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

每个环境记录都有一个[[OuterEnv]]字段,它可以是null或外部环境记录的引用。

每次运行时遇到新的代码块时,它会执行以下步骤[src]
  1. 创建一个新的环境记录。
  2. 将新环境的[[OuterEnv]]字段设置为旧的(当前活动的)环境。
  3. 返回新环境

执行上下文

为了对所有块执行此操作,使用了一个“执行上下文堆栈”,它几乎像一个“堆栈跟踪”[src]。不同之处在于,它不仅在进入和退出函数时(就像堆栈跟踪一样)只推送和弹出,而且在进入或退出代码块时(就像 if 块一样)只会更改最顶部的条目。
执行上下文是一种规范设备,用于通过 ECMAScript 实现跟踪代码的运行时评估。
执行上下文堆栈用于跟踪执行上下文。
执行上下文具有“词法环境”组件。它需要用来跟踪该特定代码块中的变量。

词法环境:用于解析执行上下文中代码中标识符引用的环境记录。[src]

词法环境 环境记录,因此它具有一个[[OuterEnv]]字段,运行时将相应地更改它。

词法环境不属于函数对象,它只属于执行上下文。

正在运行的执行上下文表示运行时当前正在执行的代码块[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.

[[环境]]

目前还没有提到函数。函数不同的是,它们可以在声明范围之外执行。

这就是 [[环境]] 的作用。

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

当一个块内部有一个函数时,运行的 LexicalEnvironment 会存储为函数对象的 [[环境]] 字段 [步骤35][步骤3][步骤14]

调用 该函数时,[[环境]] 字段将用作 [[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".

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

总结

尽管[[Environment]]LexicalEnvironment都是Environment Records,但它们用于不同的事情。

[[Environment]]保存了声明函数的LexicalEnvironment

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接