javascript - LexicalEnviroment 对象和 [[Environment]] 之间的关系

标签 javascript closures lexical-closures

据说每个代码块都有一个名为 LexicalEnviroment 的隐藏对象。 .该对象包含对外部作用域的引用和 EnviromentRecord ,其中包含有关当前作用域的信息。
另一方面,据说函数能够闭包,这要归功于 [[Enviroment]]构造“记住函数的定义位置”。
搞糊涂了,LexicalEnviroment有什么关系对象和 [[Enviroment]] ?
他们是一回事吗?只有函数有[[Enviroment]]构造?他们有 LexicalEnviroment对象呢?

最佳答案

tl;博士
它们都是 Environment Record 的实例.LexicalEnvironment只是执行上下文的一个组件(函数不能有 LexicalEnvironment ),并且在调用函数时,一个新的 LexicalEnvironment为当前上下文创建,其 [[OuterEnv]]字段被设置为函数 [[Environment]] field 。
如果是 Javascript,我想应该是:

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

免责声明:我不是该主题的专家。我只是想给你一个整体的想法,同时等待真正的专家在这里插话。
环境记录
Environment Records ,为了记录(双关语),保存要执行的功能所需的所有信息。例如,对于函数,它们保存变量声明,而 this值(value)。当然,这过于简单化[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]] 的字段。 ,它指向父环境。所以在上面的例子中,[[OuterEnv]] “EnvB”字段设置为“EnvA”[src] .

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


每次运行时遇到新的代码块时,它都会执行以下步骤 [src] :
  • 创建一个新的环境记录。
  • 设置 [[OuterEnv]]新环境到旧(当前事件)环境的领域。
  • 返回新环境

  • 执行上下文
    为了对所有块执行此操作,执行上下文堆栈 使用,这几乎就像一个 堆栈跟踪 [src] .不同之处在于,它不仅仅是在进入和退出 时推送和弹出。功能 (就像堆栈跟踪一样),它只会在进入或退出 时更改最上面的条目。代码块 (就像一个 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] :
  • 使用正确的 [[OuterEnv]] 创建一个新的环境记录值(与以前相同的步骤)。
  • 使用新的环境记录作为运行记录。
  • 评估块内的所有行。
  • 恢复到以前的环境记录。
  • 返回结果并退出块。

  • 评论前面的例子,这就是会发生的事情:
    // 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]]LexicalEnvironmentEnvironment Records ,它们用于不同的东西。[[Environment]]持有 LexicalEnvironment函数被声明的地方。LexicalEnvironment是执行上下文的一个组件,它存储有关该特定代码块中变量的信息。

    关于javascript - LexicalEnviroment 对象和 [[Environment]] 之间的关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64202695/

    相关文章:

    javascript - 使用 jPlayer 从数据库中播放音频文件

    javascript 闭包和对象引用

    javascript 柯里化(Currying)

    JavaScript 帮助 - 比较 (dd/MMM/yyyy) 格式的两个日期

    javascript - createJS 标签 HitTest

    javascript - 变量被绑定(bind)到 getCurrentLocation 函数的回调。为什么这个关闭不起作用?

    ios - UILabel 不会更新核心运动闭包内调用的文本

    lisp - macrolet 的词法闭包?

    c++ - 使用 lambda 函数模拟词法闭包是否存在不可预见的性能/实现问题?

    javascript - 使用唯一标识符将用户数据存储在 firebase 中