跳到主要内容

14 | 编译器和解释器:V8是如何执行一段JavaScript代码的?总结

前端工具和框架的自身更新速度非常块,要想追赶上前端工具和框架的更新速度, 需要抓住那些本质的知识,然后才能更加轻松地理解这些上层应用。

编译器和解释器

由于机器不能直接理解高级代码,需要通过编译器或者解析器翻译成机器码

编译器(Compiler)

编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件, 这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。 比如 C/C++、GO 等都是编译型语言。

解析器(Interpreter)

解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。 比如 Python、JavaScript 等都属于解释型语言。

image.png

V8 执行一段 JavaScript 代码流程

  1. 生成抽象语法树(AST)和执行上下文

    高级语言是开发者可以理解的语言,但是让编译器或者解释器来理解就非常困难了。 对于编译器或者解释器来说,它们可以理解的就是 AST 了。所以无论你使用的是解释型语言还是编译型语言, 在编译过程中,它们都会生成一个 AST。 这和渲染引擎将 HTML 格式文件转换为计算机可以理解的 DOM 树的情况类似。

    • 第一阶段是分词(tokenize),又称为词法分析,其作用是将一行行的源码拆解成一个个 token。
    • 第二阶段是解析(parse),又称为语法分析,其作用是将上一步生成的 token 数据,根据语法规则转为 AST。
  1. 将代码翻译成字节码(Bytecode)

    由于直接执行机器码效率高,但是运行在 512M 内存的手机上,内存占用问题也暴露出来了, 因为 V8 需要消耗大量的内存来存放转换后的机器码。为了解决内存占用问题, V8 团队大幅重构了引擎架构,引入字节码,并且抛弃了之前的编译器,

    字节码就是介于 AST 和机器码之间的一种代码。但是与特定类型的机器码无关, 字节码需要通过解释器将其转换为机器码后才能执行。

    机器码所占用的空间远远超过了字节码,所以使用字节码可以减少系统的内存使用。

  2. 执行代码 即时编译器(JIT)

    在 Ignition 执行字节码的过程中,如果发现有热点代码(HotSpot), 比如一段代码被重复执行多次,这种就称为热点代码,那么后台的编译器 TurboFan 就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时, 只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。

    Java 和 Python 的虚拟机也都是基于即时编译(JIT)(Just-In-Time Compilation)(字节码配合解释器和编译器)实现的。 具体到 V8,就是指解释器 Ignition 在解释执行字节码的同时,收集代码信息, 当它发现某一部分代码变热了之后,TurboFan 编译器就把热点的字节码转换为机器码, 并把转换后的机器码保存起来,以备下次使用。

    V8 使用了“字节码 +JIT”技术之外,苹果的 SquirrelFish Extreme 和 Mozilla 的 SpiderMonkey 也都使用了该技术。

    image.png

参考 v8