我对解释器的理解是它逐行执行程序,我们可以看到即时结果,不像转换代码的编译语言,然后执行它。
我的问题是,在Javascript中,解释器如何知道变量是在程序中的某个地方声明并将其记录为undefined
?
考虑以下计划:
function do_something() {
console.log(bar); // undefined (but in my understanding about an interpreter, it should be throwing error like variable not declared)
var bar = 111;
console.log(bar); // 111
}
隐含地理解为:
function do_something() {
var bar;
console.log(bar); // undefined
bar = 111;
console.log(bar); // 111
}
这是如何运作的?
答案 0 :(得分:14)
这个' var
提升的概念'如果你从表面上想到它,那就太混乱了。你必须深入研究语言本身的运作方式。 JavaScript是ECMAScript的一种实现,是一种解释型语言,这意味着您编写的所有代码都被送入另一个程序,而该程序依次解释代码,根据源代码的某些部分调用某些函数
例如,如果你写:
function foo() {}
解释器一旦遇到你的函数声明,就会调用一个自己的函数FunctionDeclarationInstantiation
来创建函数。解释器不是将JavaScript编译为本机机器代码,而是根据需要执行自己的C,C ++和机器代码。因为您的JavaScript代码的每个部分都被读取。它并不一定意味着逐行,所有解释都意味着不会发生编译到机器代码中。执行机器代码的单独程序会读取您的代码并动态执行该机器代码。
对于var
声明提升或任何声明,这是如何做的,是解释器首先读取所有代码而不执行任何实际代码。它分析代码并将其分成块,称为词汇环境。 Per the ECMAScript 2015 Language Specification:
8.1词汇环境
词汇环境是一种规范类型,用于根据ECMAScript代码的词法嵌套结构定义标识符与特定变量和函数的关联。词汇环境由Environment Record和可能为外部词汇环境的空引用组成。通常,词汇环境与ECMAScript代码的某些特定语法结构相关联,例如 FunctionDeclaration , BlockStatement 或 Catch 子句< em> TryStatement ,每次评估此类代码时都会创建一个新的词法环境。
Environment Record记录在其关联的词法环境范围内创建的标识符绑定。它被称为Lexical Environment的EnvironmentRecord
在执行任何代码之前,解释器会遍历您的代码,并且对于每个词法结构(例如函数声明,新块等),都会创建一个新的词法环境。在这些词汇环境中,环境记录记录在该环境中声明的所有变量,它们的值以及有关该环境的其他信息。这是允许JavaScript管理变量范围,变量查找链,this
值等的原因。
每个词汇环境都与code realm:
相关联8.2 Code Realms
在评估之前,所有ECMAScript代码都必须与 Realm 相关联。从概念上讲,领域包含一组内部对象,一个ECMAScript全局环境,在该全局环境范围内加载的所有ECMAScript代码,以及其他相关的状态和资源。
您编写的每个JavaScript / ECMAScript代码部分在实际执行任何代码之前都与领域相关联。每个领域都包含与领域相关的特定代码段所使用的内在值,领域的this
对象,领域的词汇环境等。
这意味着在执行之前会分析代码的每个词法部分。然后a realm is created包含有关该组代码的所有信息。源代码,执行它需要哪些变量,已声明哪些变量,this
是什么等等。在var
声明的情况下,当您定义像您这样的函数时,会创建一个领域在这里做了:
function do_something() {
console.log(bar); // undefined
var bar = 111;
console.log(bar); // 111
}
在这里, FunctionDeclaration 创建一个与新领域相关联的新词法环境。创建词法环境时,解释器会分析代码并查找所有声明。然后,这些声明首先在词汇环境的最开始处理,因此顶部&#39;功能:
13.3.2变量声明
var
语句声明范围为the running execution context’s VariableEnvironment的变量。 Var变量在实例化包含Lexical Environment时创建,并在创建时初始化为undefined。
因此,无论何时实例化(创建)词汇环境,都会创建所有var
声明,并将其初始化为undefined
。这意味着它们会在任何代码执行之前被处理,在顶部&#39;词汇环境:
var bar; //Processed and declared first
console.log(bar);
bar = 111;
console.log(bar);
然后,在所有之后分析您的JavaScript代码,最终执行它。由于声明是先处理的,因此会声明(并初始化为undefined
),并为您提供undefined
。
葫芦真的有点用词不当。 Hoist意味着声明直接移动到当前词法环境的顶部,而是在执行之前分析代码;什么都没动。
注意:let
和const
以相同的方式行事且也已悬挂,但这不会起作用:
function do_something() {
console.log(bar); //ReferenceError
let bar = 111;
console.log(bar);
}
这将为您提供一个ReferenceError,用于尝试访问未初始化的变量。即使let
和const
声明被提升,但规范明确声明在初始化之前无法访问它们,与var
不同:
13.3.1 Let和Const声明
let
和const
声明定义了范围为the running execution context’s LexicalEnvironment的变量。变量是在包含Lexical Environment的实例化时创建的,但在评估变量的 LexicalBinding 之前可能无法以任何方式访问。
因此,您无法访问变量,直到它被正式初始化,无论是未定义还是任何其他值。这意味着在宣布之前,您似乎无法访问它。就像你可以var
一样。
答案 1 :(得分:3)
&#34;被解释的&#34;并不意味着你的想法。
实际上,&#34;解释&#34;这意味着更像是&#34;按需编译&#34;并且,不是逐行编译(如您所想),而是以可执行代码为单位进行编译。首先将这些单元读入内存然后再执行。
在这些阶段,执行上下文的范围变得已知,声明被提升并且标识符被解析。
所有这些实现的细节都不是标准化的,每个供应商都可以随意实现它们。