执行上下文和词汇环境之间有什么区别和关系?

时间:2016-03-02 22:34:33

标签: javascript

在JavaScript中:理解奇怪的部分词法环境被解释为代码的范围,而执行上下文是词汇环境的集合,并且它包含超出编写代码的内容。

这些术语的描述在功能上仍然存在重叠,并且不清楚执行上下文的作用或执行方式。

3 个答案:

答案 0 :(得分:7)

考虑执行上下文的最佳方式是作为堆栈框架,而词汇环境确实是范围

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

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

随着执行上下文的每次更改,词法环境也会发生变化。然而,词汇环境也可能独立于此变化,例如在进入区块时。

答案 1 :(得分:3)

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

词汇环境:它是内部js引擎构造,包含标识符变量映射。 (此处标识符是指变量/函数的名称,变量是对实际对象[包括函数类型对象]或原始值的引用)。词汇环境也包含对父词汇环境的引用。

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

每个词汇环境都会跟踪其父级词汇环境(父执行上下文环境)。因此,每个函数都附加了一系列词汇环境。 [注意:在js中,函数是一个对象,通过语句创建函数意味着创建一个Function类型的对象。与其他对象一样,函数可以包含内部和用户定义的属性]

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

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

中学到的知识

答案 2 :(得分:0)

上面标记的答案将执行上下文比作堆栈框架。但是JavaScript中的执行上下文不是普通的堆栈框架。

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

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

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

JavaScript中的执行上下文分为两个阶段。第一阶段是创建阶段。在全局执行上下文中,将设置全局对象并在内存中,设置特殊变量“ this”,指向全局对象并在内存中,并且存在一个外部环境(词法环境)。当解析器开始执行上下文的创建阶段时,它首先会识别您在哪里创建了变量和函数。因此,解析器为变量和函数设置了存储空间。此步骤称为“吊装”。因此,在逐行执行特定代码之前,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引擎开始解析您的代码。这样便可以为变量分配一个值。在第一阶段,它们只是被声明并以未定义的值存储在内存中。 “未定义”是一个占位符,这是JavaScript所说“我不知道这个值是什么”的方式。这与在声明变量而不分配值时JavaScript提供的占位符相同。因此,依靠JavaScript的“提升”功能不是一个好主意。简而言之,在用var,const或let声明全局变量执行上下文之前(或在任何执行上下文中)不要使用变量。因此,这样做总是更好:

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

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

不要将自己与JavaScript内置数据类型“未定义”和解析器引发的未定义异常混淆。当未在任何地方声明变量并尝试使用它时,JavaScript引擎将引发异常“未捕获的ReferenceError:未定义[variable]”。 JavaScript表示变量不在内存中。不同于使用未定义的数据类型初始化变量。

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

function b(){
}

function a(){
  b();
}

a();

a()执行上下文将堆积在全局执行上下文的顶部。它会有自己的存储空间用于局部变量和函数。它将经历执行上下文的创建阶段和执行阶段。在执行上下文的创建阶段中,由于它没有声明任何变量或函数,因此不会为任何新变量或函数分配空间。如果确实声明了任何局部变量或函数,则它将像全局执行上下文中一样经历“吊装”过程。此外,还会为该特定功能创建一个新的特殊“ this”变量。请注意,尽管如果不使用new关键字调用该函数,则“ this”仍将引用全局对象,该对象是浏览器中的窗口对象。

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

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

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

相关问题