【问题标题】:What is the difference and relationship between execution context and lexical environment?执行上下文和词法环境有什么区别和关系?
【发布时间】:2016-06-16 00:48:02
【问题描述】:

在 JavaScript 中:理解 Weird Parts 词法环境被解释为代码的范围,而执行上下文是词法环境的集合,它包括超出您编写代码的内容。

这些术语的描述在功能上听起来仍然重叠,并且不清楚执行上下文的作用或它是如何作用的。

【问题讨论】:

  • 好问题,但不确定这是获得答案的合适地方,因为回复可能是个人意见或对规范的解释。这两个术语都在ECMA-2628.1 Lexical Environments8.3 Execution Contextss 中进行了描述。
  • 看一些coffeescript演示,所有编译的JS输出都属于他们所谓的“词法环境”,事实上,你可以在顶部看到环境中所有令牌的列表在一个长的 var 语句中。执行包括不在该列表中的内容,例如 Math.random()alert()
  • @SébastienVercammen。不,执行上下文与词法环境完全不同,词法环境有时也称为“作用域”。
  • @LifuHuang 是的,我知道。链接的 SO 线程讨论了词法环境和执行上下文,并解释了两者之间的区别。

标签: javascript


【解决方案1】:

上面标记的答案将执行上下文比作堆栈帧。但是 JavaScript 中的 Execution Context 不是普通的 Stack Frame。

在全局执行上下文中,JavaScript 引擎为您创建了两个东西,一个全局对象(一个对象是名称/值对的集合)和一个名为“this”的特殊变量。在浏览器中,全局对象是一个窗口对象。在 NodeJS 中,全局对象是另外一回事。关键是总有一个全局对象。

当您创建不在其他函数内的变量和函数时,这些变量在全局上下文中,因此会附加到全局对象,在浏览器的情况下是窗口对象。

hello = 'hello world'
> "hello world"
window.hello
> "hello world"

JavaScript 中的执行上下文是分两个阶段创建的。第一阶段是创造阶段。在全局执行上下文中,全局对象被设置并且在内存中,特殊变量'this'被设置,指向全局对象并且在内存中,并且有一个外部环境(Lexical Environment)。当解析器开始执行上下文的创建阶段时,它首先会识别您创建变量和函数的位置。因此解析器为变量和函数设置了内存空间。此步骤称为“吊装”。因此,在您的特定代码逐行执行之前,JavaScript 引擎已经为您在全局执行上下文中创建的变量和函数留出了内存空间:

console.log(a);
console.log(b());
console.log(d);

var a = 'a';
function b(){
  return 'called from b';
}

function c(){
  d = 'd';
}
> undefined
> called from b
> Uncaught ReferenceError: d is not defined

在上面的例子中,由于变量 'a' 和函数 b() 是在全局执行上下文中创建的,因此为它们分配了内存空间。请注意,尽管变量没有初始化,只是用未定义的值声明。这不是函数的情况。函数都被声明和初始化,因此函数的标识符和实际代码都存储在内存中。另请注意,由于 d(即使未使用 var、let 或 const 指定)不在全局执行上下文中,因此没有为其分配内存空间。因此,当我们尝试访问 d 标识符时会引发异常。

现在,如果我们在引用 d 变量之前调用 c(),则会评估一个新的执行上下文(它不是全局执行上下文),然后 d 将在内存中(在那个新的执行上下文中,this 关键字将指向全局对象,因为我们没有在函数调用之前放置“new”,因此 d 将附加到全局对象):

console.log(a);
console.log(b);
console.log(c());
console.log(d);

var a = 'a';
function b(){
  return 'called from b';
}

function c(){
  d = 'd';
  return 'called from c';
}

> undefined
> b() { return 'called from b' }
> called from c
> d

关于执行上下文的创建阶段的最后一点。由于发生了“提升”,因此函数定义或变量的顺序在词法范围方面并不重要。

执行上下文的第二个阶段称为执行阶段,这是发生分配的地方。 JavaScript 引擎开始解析您的代码。这就是变量将被赋值的时候。在第一阶段,它们只是被声明并以未定义的值存储在内存中。 'undefined' 是一个占位符,它是 JavaScript 表示“我还不知道这个值是什么”的方式。当你声明一个变量而不给它赋值时,这与 JavaScript 给出的占位符相同。因此,依赖 JavaScript 的“提升”功能并不是一个好主意。简单地说,在使用 var、const 或 let 声明变量之前,不要在全局执行上下文(或任何执行上下文)中使用变量。所以最好这样做:

var a = 'a';
function b(){
  return 'called from b';
}

console.log(a);
console.log(b());

不要将自己与 JavaScript 内置数据类型“未定义”与解析器引发的未定义异常混淆。当变量没有在任何地方声明并且您尝试使用它时,JavaScript 引擎将引发异常'Uncaught ReferenceError: [variable] is not defined'。 JavaScript 说变量不在内存中。这与使用未定义数据类型初始化变量不同。

除了全局执行上下文之外,函数调用还会创建一个新的执行上下文。首先,在下面的示例中,创建了一个全局执行上下文。全局执行上下文的创建阶段将由 JavaScript 引擎处理。它将创建一个全局对象(浏览器中的窗口),并将创建一个特殊变量“this”并将其指向全局对象。然后 b 和 a 函数将附加到全局对象。将为它们分配内存空间,并且由于它们是函数,因此它们的代码也将存储在内存中。如果有任何变量,它们也将存储在内存中,但它们不会被初始化,因此以未定义的数据类型存储。然后执行阶段开始。由于 JavaScript 是单线程的,它会逐行执行。在此期间,它遇到 a()。它看到'()'并且知道它必须调用函数a。创建一个新的执行上下文并将其放置在执行堆栈上。您可能知道,堆栈数据结构是后进先出的。执行上下文被压入执行堆栈,完成后从堆栈中弹出。无论最上面的上下文是什么,这就是当前正在运行的执行上下文。

function b(){
}

function a(){
  b();
}

a();

a() 执行上下文将堆叠在全局执行上下文之上。它将有自己的内存空间用于局部变量和函数。它将经历执行上下文的创建阶段和执行阶段。在 a() 执行上下文的创建阶段,由于它没有声明任何变量或函数,它不会为任何新的变量或函数分配空间。如果它确实声明了任何局部变量或函数,它将经历与全局执行上下文中相同的“提升”过程。此外,还会为该特定函数创建一个新的特殊“this”变量。请注意,如果您在没有 new 关键字的情况下调用该函数,那么 'this' 仍将引用全局对象,即浏览器中的窗口对象。

然后,它进入执行阶段。在这里它调用 b() 函数,现在创建了第三个执行上下文。这是 b() 执行上下文。它与其他执行上下文经历相同的创建和执行阶段。

当 b() 完成时,它会从堆栈中弹出,然后当 a() 完成时,它会从堆栈中弹出,然后我们返回到全局执行堆栈。重要的是,每个执行上下文都存储了一个指针,指向它在调用函数时停止的位置,从而创建了一个新的执行上下文。因此,当 b() 完成时,a() 返回到它调用 b() 的语句。然后继续执行该执行上下文中的下一条语句。同样,请记住,JavaScript 是单线程的,因此它会逐行执行。

关于词法环境的关键是它有一个链接到任何外部环境(即它的作用域链),所以它被用来解析当前执行上下文之外的标识符。最终,为每个执行上下文创建一个对应的词法环境。词法环境关心代码在你的应用程序中物理(词法)的位置。

【讨论】:

    【解决方案2】:

    执行上下文 & 执行上下文堆栈:执行上下文是跟踪函数或全局代码执行的内部 javascript 构造。 js引擎维护了一个栈——执行上下文栈或者调用栈,里面包含了这些上下文,全局的执行上下文停留在这个栈的底部。当函数开始执行时,会创建一个新的执行上下文并将其推送到堆栈中。特定的执行上下文跟踪相应函数的语句正在执行的指针。当相应函数的执行完成时,会从堆栈中弹出一个执行上下文。

    词法环境:它是保存 identifier-variable 映射的内部 js 引擎构造。 (这里的标识符是指变量/函数的名称,变量是对实际对象[包括函数类型对象]或原始值的引用)。词法环境还包含对父词法环境的引用。

    现在,对于每个执行上下文 -- 1) 创建一个相应的词法环境,以及 2) 如果在该执行上下文中创建了任何函数,则对该词法环境的引用存储在内部属性 ( [[Environment]] )的那个功能。因此,每个函数都会跟踪与其创建的执行上下文相关的词法环境。

    每个词法环境都会跟踪它的父词法环境(父执行上下文的环境)。结果,每个函数都有一个附加的词法环境链。 [注意:在js中函数是一个对象,通过语句创建一个函数就是创建一个Function类型的对象。所以像其他对象一样,函数可以保存内部和用户定义的属性]

    js引擎搜索当前词法环境中的任何变量或函数标识符,如果没有找到它会搜索附加到封闭函数的链。 (对于全局代码,此链不存在)。因此,您了解如何维护变量和函数的范围。闭包的概念也得到了这个链的支持(不是标准术语,我只是用它来简化理解)。当一个函数实例作为参数传递给另一个函数以用作回调时,它带有它的链(有点)。

    注意:答案基于我从 'Javascript Ninja 的秘密,2/e'

    中学到的知识

    【讨论】:

      【解决方案3】:

      将执行上下文视为堆栈框架的最佳方式,而词法环境确实是作用域

      各自的规范章节(§8.1 Lexical Environments§8.3 Execution Contexts)解释:

      • 执行上下文包含代码的当前评估状态、对代码(函数)本身的引用,以及可能对当前词法环境的引用。
        执行上下文在堆栈中进行管理。
      • 词法环境包含存储变量的环境记录,以及对其父环境的引用(如果有)。
        词法环境构建了一个树形结构。

      随着执行上下文的每次变化,词法环境也会发生变化。然而,词法环境也可以独立地改变,例如在进入一个块时。

      【讨论】:

        猜你喜欢
        • 2021-07-25
        • 2011-02-21
        • 2020-09-28
        • 1970-01-01
        • 2020-09-13
        • 2019-01-29
        • 2015-01-10
        • 2019-01-10
        • 2019-03-06
        相关资源
        最近更新 更多