变量环境与词汇环境

时间:2014-05-30 06:12:04

标签: javascript

我需要了解JavaScript中变量环境与词汇环境之间的区别。实际上我会通过stackoverflow和doc中提供的一些注释,但是很难理解。如果你因为我的英语知识不好而向我解释,我会很高兴

3 个答案:

答案 0 :(得分:10)

注意

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

根据ECMA-262 §10.3,变量环境是某种类型的lexical environment。两者都是"规格类型"仅用于描述ECMAScript的功能。您无论如何都无法访问它们或直接修改它们,ECMAScript实现也不必以任何特定的方式实现它们,它们必须表现为,就好像它们是规范。

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

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

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

任何想要纠正上述内容的人都请加入。

答案 1 :(得分:7)

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中包含的内容。这就是您可以从函数内访问全局变量的原因。这也是我们可以从console.log(b)块中访问while的原因,即使它位于外部范围内。

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

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

因此无法访问d

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

我认为这是它的主旨。

尽管由于let,此解释基于ECMA-262 6.0,但LexicalEnvironment在5.1中的定义类似,它用于临时绑定withcatch中的变量{1}}的{​​1}}条款。

答案 2 :(得分:1)

将词汇环境分解为另外两个基本概念(可变环境和外部环境)时,在这里更容易理解词汇环境。

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

用最简单的术语来说,变量环境指的是您创建的变量所在的位置。在下面的示例中,每个myVar是不同的,并且彼此不接触。首先,创建一个全局执行上下文。在执行上下文的创建阶段,同时创建了词法环境的外部环境和可变环境。就可变环境而言,myVar以未定义的值放置在内存中,b引用函数的定义放置在内存中。这些属性附加到“ 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关键字的情况下调用的,因此它是指全局执行上下文中的对象。这很容易证明:

var a = 1;

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

b()
> 1

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

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

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的词法环境的外部环境也会发生变化。