众所周知,我们平常写的JavaScript 代码直接交给浏览器或者 Node 执行时,底层的 CPU 是不认识的,没法执行。而我们就需要使用JavaScript引擎对代码进行编译,JavaScript 引擎的主要功能,就是结合 JavaScript 语言的特性和本质来编译执行它。
相信现在大部分前端用的都是V8引擎,也就是谷歌浏览器(Google)。V8 是由 Google 开发的开源 JavaScript 引擎,是 JavaScript 虚拟机的一种,模拟实际计算机各种功能来实现代码的编译和执行。我们可以简单地把 JavaScript 虚拟机理解成是一个翻译程序,将人类能够理解的 编程语言 JavaScript,翻译成机器能够理解的机器语言。
V8是如何编译JavaScript代码的?
V8 执行一段 JavaScript 代码所经历的主要流程包括:
-
初始化基础环境;
-
解析源码生成 AST 和作用域;(Parser)
-
依据 AST 和作用域生成字节码;(Ignition)
-
解释执行字节码;
-
监听热点代码;(TurboFan)
-
优化热点代码为二进制的机器代码;(TurboFan)
-
反优化生成的二进制机器代码。
简单地说,Parser 将 JS 源码转换为 AST,然后 Ignition 将 AST 转换为 Bytecode,最后 TurboFan 将 Bytecode 转换为经过优化的 Machine Code(实际上是汇编代码)。
如果函数没有被调用,则 V8 不会去编译它。
如果函数只被调用 1 次,则 Ignition 将其编译 Bytecode 就直接解释执行了。TurboFan 不会进行优化编译,因为它需要 Ignition 收集函数执行时的类型信息。这就要求函数至少需要执行 1 次,TurboFan 才有可能进行优化编译。
如果函数被调用多次,则它有可能会被识别为热点函数,且 Ignition 收集的类型信息证明可以进行优化编译的话,这时 TurboFan 则会将 Bytecode 编译为 Optimized Machine Code(已优化的机器码),以提高代码的执行性能。
图片中的红色虚线是逆向的,也就是说 Optimized Machine Code 会被还原为 Bytecode,这个过程叫做 Deoptimization。这是因为 Ignition 收集的信息可能是错误的。
function sum(a,b){
return a+b
}
sum(1,2)
sum(2,3)
sum(3,4)
sum('1','2')
比如 sum 函数的参数之前是整数,多次调用后被识别为热点函数,后来又变成了字符串。生成的 Optimized Machine Code 已经假定 sum 函数的参数是整数,那当然是错误的,于是需要进行 Deoptimization(反优化)从而消耗性能。
TypeScript在参数上将函数明确规定类型后即可避免此类问题的发生,从而提高代码的执行性能。
我想应该也是使用TypeScript的好处之一。