繁体   English   中英

变量环境与词法环境

[英]Variable Environment vs lexical environment

我需要了解 JavaScript 中变量环境与词法环境之间的区别。 实际上,我浏览了stackoverflow和文档中的一些注释,但它们很难理解。如果你能简单地向我解释一下,我会很高兴,因为我的英语知识不好

笔记

这个答案与 ECMA-262 ed 5.1 有关。 后来的版本修改了变量和词法环境的描述,以适应letconst的词法作用域(它们都是块作用域)。

根据ECMA-262 §10.3 ,变量环境是某种类型的词法环境 两者都是“规范类型”,仅用于描述 ECMAScript 的特性。 无论如何,您都无法直接访问或修改它们,ECMAScript 实现不必以任何特定方式实现它们,它们只需要表现得好像它们是按照规范实现的一样。

词法环境由一个环境记录组成,它可以被认为是一个对象,其属性是在相关执行上下文中声明的变量和函数名。 对于函数,它还具有来自函数声明或表达式中形式参数列表的标识符(例如function foo(a, b){}有效地ab声明为foo的环境记录中的变量)。

词法环境还具有到任何外部词法环境(即其作用域链)的链接,因此它用于解析当前执行上下文之外的标识符(例如,函数内的全局变量)。 除了函数和执行上下文之外,它们还可以与其他结构相关联,例如try..catchwith语句。

变量环境只是执行上下文中词法环境的一部分,本质上只是在当前上下文中声明的变量和函数。

任何想更正上述内容的人,请加入。

LexicalEnvironmentVariableEnvironment在运行时跟踪变量,分别对应块作用域和函数/模块/全局作用域。 这是一个基于我阅读规范http://www.ecma-international.org/ecma-262/6.0/的示例

0:  function do_something() {
1:     var a = 1;
2:     let b = 2;
3:     while (true) {
4:         var c = 3;
5:         let d = 4;
6:         console.log(b);
7:         break;
8:     }
9:  }
10:
11: do_something();

当我们第一次调用do_something()时,它会创建一个ExecutionContext

ExecutionContext:
    LexicalEnvironment:
        b -> nothing
        outer: VariableEnvironment //here should VariableEnvironment
    VariableEnvironment:
        a -> undefined, c -> undefined
        outer: global
    ...

进入while循环会创建一个新的词法环境:

ExecutionContext:
    LexicalEnvironment:
        d -> nothing
        outer:
            LexicalEnvironment
                b -> 2
                outer: global
    VariableEnvironment:
        a -> 1, c -> undefined
        outer: global
    ...

现在,当我们查找变量时,我们总是可以依靠outer中包含的任何内容。 这就是为什么您可以从函数内部访问全局变量的原因。 这也是为什么我们可以从while块中访问console.log(b)的原因,即使它位于外部范围内。

当我们离开 while 块时,我们恢复了原来的词法环境。

ExecutionContext:
    LexicalEnvironment
        b -> 2
        outer: global
    VariableEnvironment:
        a -> 1, c -> 3
        outer: global

因此d不再可访问。

然后,当我们离开函数时,我们会破坏执行上下文。

我认为这就是它的要点。

虽然这个解释是基于 ECMA-262 6.0 因为let ,但 LexicalEnvironment 在 5.1 中的定义类似,它用于临时绑定变量withtry/catchcatch子句。

当您将词法环境分解为两个更底层的概念,即变量环境和外部环境时,在这里更容易理解词法环境。

每个执行上下文都有一个外部环境和一个变量环境。 外部环境和变量环境组成了词法环境。 也就是说,变量环境是某种类型的词法环境。 词法环境可以被认为是一个内部的 JavaScript 引擎结构,它包含标识符变量映射。 标识符是变量或函数的名称,变量是对标识符所存储的实际数据类型的引用(例如,对象、数字、字符串、布尔值、空值、未定义)。 词法环境还具有到任何外部环境(即其范围链)的链接,因此它用于解析当前执行上下文之外的标识符。 最终,为每个执行上下文创建一个对应的词法环境。

简单来说,变量环境是指您创建的变量所在的位置。 在下面的示例中,每个 myVar 都是不同的,它们不会相互接触。 首先,创建一个全局执行上下文。 在执行上下文的创建阶段,词法环境的外部环境和变量环境都被创建。 就变量环境而言,myVar 以未定义的值放置在内存中,而 b 和 a 函数通过对其定义的引用被放入内存。 这些属性附加到“this”引用的全局对象。 我们在全局执行上下文中定义的属性存储在全局变量环境中。 然后在执行上下文的执行阶段,myVar 被赋值为 1。所以现在在全局执行上下文的内存中,myVar 的值为 1。这存储在全局变量环境中。

然后在执行期间,调用 a 函数。 创建一个新的执行上下文并将其放入执行堆栈。 它经历了执行上下文的创建和执行阶段。 此处声明的 myVar 变量被放置在与其他执行上下文分开的内存中的新区域中。 创建了一个词法环境,将标识符映射到其变量。 它实际上为此执行上下文中定义的任何变量创建了一个变量环境。 此变量环境不同于任何其他变量环境。 执行阶段发生,myVar 被赋值为 2。现在在这个变量环境中,myVar 的值为 2,而在全局 ExecutionContext 中,myVar 的值为 1。请注意,如果我们在如果当前执行上下文中不存在这个新的执行上下文,则词法环境将在其父词法环境中搜索变量,即外部环境。 由于 JavaScript 是单线程的,一旦 myVar 被赋值为 2,它就会进入下一条语句,即 b 的调用,并为 b 创建一个新的执行上下文,并再次发生相同的过程。

function b(){
  var myVar;
  console.log(myVar);
}

function a(){
  var myVar = 2;
  console.log(myVar);
  b();
}

var myVar = 1;
console.log(myVar);
a();
console.log(myVar);

> 1
> 2
> undefined
> 1

再次强调,每个 myVar 都存在于各自的变量环境中,与它们自己的执行上下文相对应,这一点很重要。 因此,当我们调用 a 函数后 console.log(myVar) 时,全局执行上下文中的 myVar 的值仍然是 1。实际上,当我们执行第二个 console.log(myVar) 时,a 和 b 函数的执行上下文将已经被弹出。

这里也很重要的一点要注意,由于这些函数是在没有 new 关键字的情况下调用的,所以 this 指的是全局执行上下文中的对象。 这很容易证明:

var a = 1;

function b(){
    var a = 2;
    console.log(this.a);
}

b()
> 1

上面,this.a 指的是在全局执行上下文中定义的 a,因为 this 指的是全局对象,在浏览器中是 Window 对象。

现在我们讨论了变量环境,让我们讨论词法环境的外部环境。 这将我们引向作用域链。 首先,我们必须问什么是执行上下文的外部环境? 在下面的示例中,对于函数 b,其外部环境是全局执行上下文。 这也是函数 a 的情况。 对于函数 b 也是如此,即使函数 a 在执行堆栈中直接位于函数 b 之下。 外部环境调用了词法环境的概念。 词法环境强调了在代码中物理写入的位置很重要的想法。 它决定了标识符/变量映射如何存在于您的代码中,以及它们如何相互连接。 所以我们必须问自己,函数 b 在词汇上位于哪里? 它在词汇上位于全局外部环境之上。 函数 b 不在函数 a 的内部; 相反,它与全球外部环境处于同一水平。 当您在任何特定的执行上下文中运行一行代码时请求变量时,如果引擎在当前执行上下文的变量环境中找不到变量,它将在当前执行上下文的外部环境中查找变量. 现在即使有 10 个执行上下文堆叠在一起,如果第 10 个执行上下文在词法上位于全局外部环境中,当它在其他 9 个执行上下文中的每一个中搜索其外部环境时,它将搜索所有全局执行上下文的方式,因为代码在词法上位于外部环境中。 这个搜索给定执行上下文的外部环境的过程在 JavaScript 中被称为作用域链。 记住范围询问“我在哪里可以访问变量?”。 范围链是外部环境引用的那些链接。

function b(){
  console.log(myVar);
}

function a(){
  var myVar = 2;
  b();
}

var myVar = 1;
a();
> 1

您可能认为 b 中的 myVar 为 2,因为 b 函数会查看其父执行上下文,即 a(它是执行堆栈中 b 的执行上下文正下方的执行上下文)。 但这不是词汇环境的外部环境的工作方式。 因为 b 的外部环境是全局执行上下文,所以 b 中的 myVar 将是值 1。

现在可以更改函数的词法环境。 我们可以通过将函数 b 物理放置在函数 a 内部来改变函数 b 的词法环境。 由于我们改变了它的物理位置,函数 b 的词法环境的外部环境发生了变化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM