文章目录
- 前言
- 一、编译器和解释器
- 二、V8执行JavaScript代码过程
- 1.生成AST(抽象语法树)
- 2.生成字节码
- 3.生成机器码
- 总结
前言
相信大家对Babel已经不陌生了,Babel充斥在我们代码中的每个角落。比如:jsx转化成js,es6转化成es5…
一切需要代码转化的事都可以使用babel来做,但我们有没有想过,babel是怎么去进行代码转化的呢,它转化的过程中发生了什么,让我们一起来了解一下js的编译原理吧!
一、编译器和解释器
在了解之前,我们先来理解一下编译器和解释器的概念吧。
我们知道,我们所编写的代码是不能直接被机器识别的,需要转化成能被机器识别的机器语言。我们都知道,javascript是一门解释型语言,后端之王java是一门编译型语言,我们根据语言的执行流程可以分为解释型和编译型。我们来看看他们的区别吧。
- 解释型语言:需要将代码转换成机器码,但是和编译型的区别在于运行时需要转换。比较显著的特点是,解释型语言的执行速度要慢于编译型语言,因为解释型语言每次执行都需要把源码转换一次才能执行。
- 编译型语言:在代码运行前编译器直接将对应的代码转换成机器码,运行时不需要再重新翻译,直接可以使用编译后的结果。
目前市面上有很多种 JS 引擎,其中比较现代的 JS 引擎就是 V8,它引入了 Java 虚拟机和 C++ 编译器的众多技术,和早期的 JS 引擎工作方式已经有了很大的不同。V8 是众多浏览器的 JS 引擎中性能表现最好的一个,并且它是 Chrome 的内核,Node.js 也是基于 V8 引擎研发的。
编译器和解释器翻译代码流程如下:
编译器:
解释器:
大概可以描述为:
编译器:
- 在代码的编译中,编译器首先会把代码经过词法分析和语法分析成AST
- 然后经过优化变成可执行的文件
- 如果过程中有出现语法等错误,将会报错且不会生成可执行文件。
解释器:
- 在代码的编译中,解释器首先会把代码经过词法分析和语法分析成AST
- 解释器还会将AST进行词义分析生成字节码
- 根据字节码来生成程序,执行结果。
回归正题,让我们来看一看V8是怎么执行JavaScript代码的!
二、V8执行JavaScript代码过程
通常来说,我们可以将V8执行过程分为4个阶段:
- Parse 阶段:V8 引擎负责将 JS 代码转换成 AST(抽象语法树)
- Ignition 阶段:解释器将 AST 转换为字节码,解析执行字节码也会为下一个阶段优化编译提供需要的信息
- TurboFan 阶段:编译器利用上个阶段收集的信息,将字节码优化为可以执行的机器码
- Orinoco 阶段:垃圾回收阶段,将程序中不再使用的内存空间进行回收
1.生成AST(抽象语法树)
此阶段分为两个步骤:
- 词法分析:讲代码语句分割成最小的单元,称为token。例如:let a = 0;,分割成let,a,=,0,;五个单元,js会自动忽略空格单元
- 语法分析:讲token数据通过语法检测转换成AST,若语法不规范,则抛出错误
tips:babel的原理就是利用AST,把代码进行转换。Eslint也是通过将代码转化成AST,然后通过语法分析检测代码规范性的问题。
2.生成字节码
为什么要生成字节码?如果讲AST直接转换成机器码的话,会出现下面的问题:
- 直接转换会带来内存占用过大的问题,因为将抽象语法树全部生成了机器码,而机器码相比字节码占用的内存多了很多
- 某些 JavaScript 使用场景使用解释器更为合适,解析成字节码,有些代码没必要生成机器码,进而尽可能减少了占用内存过大的问题
字节码就是介于 AST 和机器码之间的一种代码。需要将其转换成机器码后才能执行,字节码可以理解为是机器码的一种抽象。Ignition 解释器除了可以快速生成没有优化的字节码外,还可以执行部分字节码。
3.生成机器码
在 Ignition 解释器处理完之后,如果发现一段代码被重复执行多次的情况,生成的字节码以及分析数据会传给 TurboFan 编译器,它会根据分析数据的情况生成优化好的机器码。再执行这段代码之后,只需要直接执行编译后的机器码,这样性能就会更好。
对于 TurboFan 编译器,它是 JIT(即时编译) 优化的编译器,因为 V8 引擎是多线程的,TurboFan 的编译线程和生成字节码不会在同一个线程上,这样可以和 Ignition 解释器相互配合着使用,不受另一方的影响。
总结
本篇博客借鉴了多位大佬的文章,每次阅读都会有新的理解,只有了解底层原理才能让自己在前端的道路上越走越远。