本文已参与掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力计划|创作者训练营第三期正在进行,「写」出个人影响力。 接触一个新技能(知识点),我们往往会经历下面这样的思考流程:它是什么,我为什么要了解或学习它,学习它能给我带来什么收益?发问三连击。好,那么下文也按照这三点来一一说明,为什么需要了解学习V8。
什么是V8
V8是谷歌发布的一个开源JavaScript引擎,底层使用C++编写,用来执行JavaScript(简称'JS'),是Chrome浏览器Chrome内核的JS引擎;我们熟知的nodejs也使用V8来执行js。
我们可以先简单的把V8当成是一个翻译程序,将工程师编写的程语言 JavaScript,翻译成机器能够理解的机器语言。
为什么需要了解V8的执行流程
目前业内有不少JS引擎,如SpiderMonkey 、jsCore 、rhino等,但其中以V8性能最为出色,应用最为广泛,代表这业内最高的水平;如在国内,pc端的360、QQ、搜狗等常用浏览器,以及安卓系统的原生浏览器,都使用了Chrome内核;从列举的浏览器可以看出,作为一名前端开发者,我们写的js代码,大多数都跑在v8上,所以我们有必要了解下V8是如何执行js的。
执行流程
从学习JavaScript的第一天起,我们就被告知,js是一门解释型、动态脚本语言。不同于Java、C++等编译型,运行前需要编译器编译成机器能懂的二进制文件;而解释型语言,每次运行都需要解释器对其进行动态解释和执行。
从上图可以看到,V8在执行过程需要解释器、编译器的参与,那么它们是如何配合去执行一段 JavaScript代码的呢? 接下来我们就按照上图来一一分解其执行流程。
生成抽象语法树(AST)
第一步将js源码生成AST。对于高级编程语言,解释器和编译器是不能理解的,首先得把它转为让解释器、编译器都能懂的东西——AST。 借助在线工具,我们来看看js代码对应的AST是什么:
function sayHello() {
var msg = '你好,掘金!'
return msg;
}
sayHello();
对应的AST是这样的:
{
"type": "Program",
"start": 0,
"end": 70,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 58,
"id": {
"type": "Identifier",
"start": 9,
"end": 17,
"name": "sayHello"
},
"expression": false,
"generator": false,
"async": false,
"params": [],
"body": {
"type": "BlockStatement",
"start": 20,
"end": 58,
"body": [
{
"type": "VariableDeclaration",
"start": 24,
"end": 42,
"declarations": [
{
"type": "VariableDeclarator",
"start": 28,
"end": 42,
"id": {
"type": "Identifier",
"start": 28,
"end": 31,
"name": "msg"
},
"init": {
"type": "Literal",
"start": 34,
"end": 42,
"value": "你好,掘金!",
"raw": "'你好,掘金!'"
}
}
],
"kind": "var"
},
{
"type": "ReturnStatement",
"start": 45,
"end": 56,
"argument": {
"type": "Identifier",
"start": 52,
"end": 55,
"name": "msg"
}
}
]
}
},
{
"type": "ExpressionStatement",
"start": 59,
"end": 70,
"expression": {
"type": "CallExpression",
"start": 59,
"end": 69,
"callee": {
"type": "Identifier",
"start": 59,
"end": 67,
"name": "sayHello"
},
"arguments": [],
"optional": false
}
}
],
"sourceType": "module"
}
上面生成的AST是个JSON格式的数据,有点复杂,我们可以看下图经过收起简化的,body里有两个对象,刚好和实例代码的两个部分(函数声明、调用)对应上了,其实你也可以把 AST 看成代码的结构化的表示,编译器或者解释器后续的工作都需要依赖于 AST,而不是源代码。
其实AST是很重要的一种数据结构,我们前端开发在日常开发中,几乎每天都在被动或主动的和它打交道。比如我们在开发环境编写ES6+代码时,生产环境需要将其转为ES5代码时使用的Babel,就是依赖AST的代码转换器:首先把ES6+源码转换为AST,然后再将ES6+的AST转换为ES5语法的AST,最后利用ES5的AST生成JavaScript源代码。我们使用来检查语法的ESLint,也是用了AST。
生成 AST 需要经过两个阶段,分别是词法分析和语法分析。
首先进行词法分析:将一行行的源码拆解成一个个token。token是指语法上不可能再分的、最小的单个字符或字符串。如下图,关键字、标识符、赋值以及字符串部分都是一个token.
拆解token后,接着进行语法分析,把上一步生成的token数据,根据语法规则转为AST。如果源码符合语法规则,这一步就会顺利完成。否则这一步就会终止,并抛出一个“语法错误”。
生成了AST后,那接下来 V8 就会生成该段代码的执行上下文。
生成字节码
经过一系列操作后,得到了AST和执行上下文后,解释器就会根据AST生成字节码,并解释执行字节码。这里可能会有疑问,不是应该转为执行效率更高的机器码吗。事情是这样子的,一开始V8是把AST直接转为机器码的,让它获得了显著的性能提升;但随着移动设备的普及,特别是在手机端,内存空间有限,我们知道机器码其实就是一堆二进制数据,很占空间,所以导致V8的这种方式,极大的消耗内存。后来为了解决这个问题,V8引入了字节码。因为字节码体积比机器码的小得多,减少了系统内存的占用。
执行代码
有了字节码后,接下来解释器就开始解释执行字节码了。在解释器执行字节码的过程中,如果发现有热点代码,就是某段代码被重复执行了多次,就称为热点代码,那么编译器就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。 至此,V8执行JavaScript代码的流程已讲完,希望能给到大家一些思考和帮忙!