浏览器是否真正逐行读取JavaScript还是多次通过?

use*_*506 18 javascript browser interpreter

我理解JavaScript是解释的而不是编译的.没问题.但是,我在这里继续阅读,JavaScript是"即时"执行的,并且每行读取一行.对于以下示例,这个想法让我感到困惑:

writeToConsole();

function writeToConsole() {
    console.log("This line was reached.");
}
Run Code Online (Sandbox Code Playgroud)

为了记录,这段代码将写入控制台就好了.仍然,浏览器如何知道exampleFunction()它是否尚未到达函数的存在?

换句话说,什么时候首先解释这个函数?

jos*_*736 48

首先,你做了一个不正确的假设:编译现代JavaScript.V8,SpiderMonkey和Nitro 等引擎将 JS源代码编译到主机平台的本机代码中.

即使在较旧的引擎中,JavaScript也不会被解释.它们将源代码转换为字节码,引擎的虚拟机执行该字节码.

这实际上是Java和.NET语言中的工作方式:当您"编译"应用程序时,您实际上是将源代码分别转换为平台的字节码,Java字节码CIL.然后在运行时,JIT编译器将字节码编译为机器代码.

只有非常古老和简单的JS引擎才能解释 JavaScript源代码,因为解释非常慢.

那么JS编译是如何工作的呢?在第一阶段,源文本被转换为抽象语法树(AST),这是一种数据结构,以机器可以处理的格式表示您的代码.从概念上讲,这很像HTML文本转换为DOM表示形式,这正是您的代码实际使用的方式.

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

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

一旦引擎有AST:

  • 解释器可能只是直接从AST开始执行指令.这很慢.
  • JS VM实现使用AST生成字节码,然后开始执行字节码.
  • 编译器使用AST 生成 CPU执行的机器代码.

所以你现在应该能够看到,至少 JS执行分两个阶段进行.

但是,执行阶段确实对您的示例工作原理没有影响.它的工作原理是定义了如何评估和执行JavaScript程序的规则.规则可以很容易地编写,使得您的示例不起作用,而不会影响引擎本身实际解释/编译源代码的方式.

具体而言,JavaScript具有通常称为提升的功能.为了理解提升,您必须了解函数声明函数表达式之间的区别.

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

function foo() {

}
Run Code Online (Sandbox Code Playgroud)

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

var foo = function() { };

$.get('/something', function() { /* callback */ });
Run Code Online (Sandbox Code Playgroud)

JavaScript要求在执行上下文的开头将函数声明(第一种类型)分配给变量名,而不管声明在源文本(上下文中)中出现的位置.一个执行上下文大致equatable到范围 -在平原而言,在函数内部的代码或脚本的最顶端,如果没有一个函数内部.

这可能导致非常奇怪的行为:

var foo = function() { console.log('bar'); };

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

foo();
Run Code Online (Sandbox Code Playgroud)

您希望将哪些内容记录到控制台?如果您只是线性地阅读代码,您可能会想到baz.但是,它实际上会记录bar,因为声明foo是在指定的表达式之上悬挂的foo.

总结如下:

  • JS源代码永远不会逐行"读取".
  • JS源代码实际上是在现代浏览器中编译的(真正意义上的).
  • Engines在多次传递中编译代码.
  • 行为是您的示例是JavaScript语言规则的副产品,而不是如何编译或解释.


Bra*_*d M 9

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

然而,

var foo = function(){};
Run Code Online (Sandbox Code Playgroud)

这将不会被检查,因此以下将抛出一个TypeError: undefined is not a function

foo();
var foo = function(){};
Run Code Online (Sandbox Code Playgroud)

  • 我相信"_hoisting_"这个词在这里发挥作用.如果使用函数声明,则函数将为"_hoisted_".[进一步阅读](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting). (6认同)

Ale*_*yne 8

确实需要2次通过.第一遍解析语法树,其中一部分正在执行提升.提升是使您发布的代码有效的原因.提升将任何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(
Run Code Online (Sandbox Code Playgroud)

http://jsfiddle.net/Z86rj/

没有alert()发生在这里.第一次传递解析失败,并且没有代码执行.