浏览器真的是逐行读取JavaScript还是进行多次传递?

浏览器真的是逐行读取JavaScript还是进行多次传递?,javascript,browser,interpreter,Javascript,Browser,Interpreter,我知道JavaScript是解释的,而不是编译的。没问题。然而,我一直在这里读到JavaScript是“动态”执行的,并且一次只读取一行。当涉及到以下示例时,这个想法让我相当困惑: writeToConsole(); function writeToConsole() { console.log("This line was reached."); } 作为记录,这段代码可以很好地写入控制台。但是,如果浏览器还没有到达函数,它怎么知道存在exampleFunction() 换言之,此

我知道JavaScript是解释的,而不是编译的。没问题。然而,我一直在这里读到JavaScript是“动态”执行的,并且一次只读取一行。当涉及到以下示例时,这个想法让我相当困惑:

writeToConsole();

function writeToConsole() {
    console.log("This line was reached.");
}
作为记录,这段代码可以很好地写入控制台。但是,如果浏览器还没有到达函数,它怎么知道存在
exampleFunction()


换言之,此函数究竟何时首次解释?

在执行任何代码之前,浏览器将首先检查所有函数

但是,

var foo = function(){};
这将不会被检查,因此下面将抛出一个
TypeError:undefined不是函数

foo();
var foo = function(){};

脚本首先被解析,然后被解释,然后被执行。当执行第一条语句(
writeToConsole();
)时,函数声明已被解释


由于所有变量和都在当前作用域(在您的情况下为全局脚本作用域)中提升,因此您将能够调用下面声明的函数。

它需要两次传递。第一步解析语法树,其中一部分正在执行提升。这就是你发布的代码工作的原因。提升将任何
var
或命名函数声明
function fn(){}
(但不是函数表达式
fn=function(){}
)移动到它们出现的函数的顶部

第二个过程执行解析的、提升的以及在某些引擎中编译的源代码树

看看这个例子。它显示了语法错误如何通过在第一个过程中抛出一个扳手来阻止脚本的所有执行,从而阻止第二个过程(实际代码执行)的发生

var validCode = function() {
  alert('valid code ran!');
};
validCode();

// on purpose syntax error after valid code that could run
syntax(Error(


此处不出现
alert()
。第一次解析失败,没有代码执行。

JavaScript实际上是逐行解释的。但是,在它被执行之前,编译器会进行第一步,阅读中的某些内容(非常古怪,看看这个:如果你真的感兴趣的话)

关键是,JavaScript将首先被编译器“读取”,编译器已经存储了定义为
function foo(){…}
的函数。您可以在脚本中的任何给定时间调用这些对象,前提是您正在从同一范围或从属范围调用它们。现代编译器还做的是预分配对象,因此作为一个副作用,为了性能问题,强烈地键入变量是有意义的


编译器不会存储
var foo=function()。像、和Nitro这样的引擎将JS源代码编译到主机平台的本机中

即使在较旧的引擎中,JavaScript也不会被解释。它们将源代码转换为引擎执行的源代码

事实上,Java和.NET语言就是这样工作的:当你“编译”你的应用程序时,你实际上是在将源代码转换成平台的字节码,然后分别转换。然后在运行时,a将字节码编译成机器码

实际上,只有非常古老和简单的JS引擎才支持JavaScript源代码,因为解释速度非常慢

那么JS编译是如何工作的呢?在第一阶段,源文本被转换成一种数据结构,它以机器可以处理的格式表示代码。从概念上讲,这非常类似于HTML文本如何转换为其表示形式,这就是您的代码实际使用的内容

为了生成AST,引擎必须处理原始字节的输入。这通常是由一个团队来完成的。lexer并不真正“逐行”读取文件;而是逐字节读取,使用语言语法规则将源文本转换为标记。然后,lexer将令牌流传递给,这就是实际构建AST的部分。解析器验证令牌是否构成有效序列

您现在应该能够清楚地看到为什么语法错误会阻止代码工作。如果源文本中出现意外字符,引擎将无法生成完整的AST,并且无法进入下一阶段

一旦发动机具有AST:

  • 解释器可以直接从AST开始执行指令。这很慢
  • JSVM实现使用AST生成字节码,然后开始执行字节码
  • 编译器使用AST进行编译,CPU执行AST
所以您现在应该可以看到,JS执行至少分两个阶段进行

但是,执行阶段实际上对示例的工作原因没有影响。它之所以能够工作,是因为它定义了如何评估和执行JavaScript程序。这些规则可以很容易地编写,这样您的示例就不会工作,而不会影响引擎本身实际解释/编译源代码的方式

具体来说,JavaScript通常被称为提升。为了理解函数,您必须理解函数声明和函数表达式之间的区别

简单地说,函数声明是当您声明一个将在别处调用的新函数时:

function foo() {

}
函数表达式是指在需要表达式的任何位置(如变量赋值或参数中)使用
function
关键字:

var foo = function() { };

$.get('/something', function() { /* callback */ });
JavaScript要求在执行上下文开始时将函数声明(第一种类型)分配给变量名,,而不考虑声明在源文本(上下文)中出现的位置。执行上下文
var foo = function() { console.log('bar'); };

function foo() { console.log('baz'); }

foo();