作为一名前端程序员,每天上班第一件事就是打开电脑,不由自主地点开chrome浏览器,或者摸摸fishfor一会儿或立即进入工作状态。接下来,浏览器窗口会陪你度过一天,一般会到七八点,后来就是九十点,再晚一点就会陪你度过一天,时刻关注你的工作。作为你忠实的伙伴,你问问自己,你是否认真了解它是如何运作的?你有没有走进过它的内心世界?如果你也好奇,请收看本期的《走进chrome内心,了解V8引擎是如何工作的》。什么是V8?在深入了解一件事之前,你需要知道它是什么。V8是一个用C++编写的高性能JavaScript和WebAssembly引擎,由Google开源,用于Chrome和Node.js。它实现了ECMAScript和WebAssembly,并运行在Windows7及更高版本、macOS10.12+和使用x64、IA-32、ARM或MIPS处理器的Linux系统上。V8可以独立运行,也可以嵌入任何C++应用程序中。V8的由来接下来我们来关心一下它是如何诞生的,以及为什么叫这个名字。V8最初是由LarsBak的团队开发的。它以汽车的V8发动机(八缸V型发动机)命名,预示着它将是一个具有极高性能的JavaScript引擎。于2008年9月2日同时上映。chrome与开源一起发布。为什么需要V8?我们写的JavaScript代码最终会在机器中执行,但是机器无法直接识别这些高级语言。它需要经过一系列的处理,将高级语言转换成机器可以识别的指令,即二进制代码,交给机器执行。中间的转换过程是V8的具体工作。接下来,让我们详细了解一下。V8的构成我们先来看看V8的内部构成。V8内部有很多模块,其中最重要的有以下四个:Parser:解析器,负责将源代码解析成ASTIgnition:解释器,负责将AST转换成字节码并执行,标记热代码TurboFan:编译垃圾收集器负责将热点代码编译成机器码并执行Orinoco:垃圾收集器负责内存空间的回收V8工作流程下面是V8中几个重要模块的具体工作流程。我们一一分析。Parser解析器Parser解析器负责将源代码转换成抽象语法树AST。转换过程中有两个重要的阶段:词法分析和句法分析。词法分析,也称为分词,是将字符串形式的代码转换为标记序列的过程。这里的token是一个字符串,是源代码的最小单位,类似于英文中的单词。词法分析也可以理解为将英文字母组合成单词的过程。词法分析时不关心词与词之间的关系。例如:在词法分析过程中可以将括号标记为token,但不检查括号是否匹配。JavaScript中的token主要有以下几种类型:关键字:var,let,const等标识符:不带引号的连续字符,可能是一个变量,if,else,或者true,false这些内置的关键字常量运算符:+、-、*、/等数字:十六进制、十进制、八进制等字符串,科学表达式:变量值等空格:连续空格、换行、缩进等注意:行注释或块注释是不可分割的最小语法单位。标点符号:花括号、圆括号、分号、冒号等。以下是esprima词法分析后consta='helloworld'生成的token。[{“类型”:“关键字”,“值”:“常量”},{“类型”:“标识符”,“值”:“a”},{“类型”:“标点符号”,“值”:"="},{"type":"String","value":"'helloworld'"}]语法分析语法分心是将词法分析生成的token按照给定形式的语法过程转化为AST。即单词组合成句子的过程。转换过程中会校验语法,如果语法错误会抛出语法错误。上面的consta='helloworld'语法分析后生成的AST如下:type":"VariableDeclarator","id":{"type":"Identifier","name":"a"},"init":{"type":"Literal","value":"helloworld","raw":"'helloworld'"}}],"kind":"const"}],"sourceType":"script"}Parser解析器生成的AST会被Ignition解释器处理。Ignition解释器Ignition解释器负责将AST转换成字节码(Bytecode)并执行。字节码是介于AST和机器码之间的代码。它与特定类型的机器代码无关。它需要通过解释器转换成机器码才能执行。看到这里,想必大家心存疑惑。既然字节码也需要转换成机器码才能运行,为什么不直接将AST转换成机器码直接运行呢?转成机器码直接运行肯定更快,为什么还要加一个中间过程呢?其实在V8的5.9版本之前,是没有字节码的,而是将JS代码直接编译成机器码存放在内存中,占用内存很大,早期手机的内存也不高。过度占用会导致手机性能大幅下降;而直接编译成机器码会导致编译时间长,启动速度慢;而且,直接将JS代码转为机器码,需要针对不同的CPU架构编写不同的指令集。很高。5.9版本后引入字节码,可以解决上述内存占用大、启动时间长、代码复杂度高的问题。接下来我们看看Ignition是如何将AST转换成字节码的。下图是Ignition解释器的工作流程图。AST需要先经过字节码生成器,然后经过一系列的优化,才能生成字节码。优化包括:RegisterOptimizer:主要是为了避免不必要的寄存器加载和存储PeepholeOptimizer:找到字节码中可重用的部分,并合并Dead-codeElimination:删除无用代码,减少字节码将代码转换成字节码后,可以通过口译员。Ignition在执行过程中,会监控代码的执行,记录执行信息,比如函数的执行次数,函数每次执行传递的参数等等。当同一段代码被多次执行时,就会被标记为热代码。热点代码将交给TurboFan编译器进行处理。TurboFan编译器TurboFan得到Ignition标记的热点代码后,首先对其进行优化,然后将优化后的字节码编译成更高效的机器码并存储。下次再次执行相同的代码时,会直接执行对应的机器码,大大提高了代码的执行效率。当一段代码不再是热代码时,TurboFan会进行反优化过程,将优化后的编译机器码还原为字节码,并将代码的执行权交还给Ignition。下面我们来看看具体的实现过程。以sum+=arr[i]为例。由于JS是一门动态类型的语言,sum和arr[i]可能每次都是不同的类型。执行这段代码时,Ignition每次都会检查sum和arr[i]。arr[i]的数据类型。当发现同一代码被多次执行时,将其标记为热代码并交给TurboFan。TurboFan在执行的时候,每次都要判断sum和arr[i]的数据类型,很浪费时间。所以在优化的时候,sum和arr[i]的数据类型会根据之前的执行情况来确定,编译成机器码。下次执行时,省略判断数据类型的过程。但是,如果在后续执行过程中arr[i]的数据类型发生变化,之前生成的机器码不符合要求,TurboFan会丢弃之前生成的机器码,将执行权交由Ignition完成,以优化流程。热点代码:优化前:优化后:总结现在总结一下V8的执行过程:源码经过Parser解析器,通过Parser解析器生成ASTAST,通过Ignition解释器生成字节码并在执行时执行。如果找到热代码,将热代码交给TurboFan编译器生成机器码并执行。如果热点代码不再满足要求,则对其进行反优化。这种将字节码与解释器、编译器相结合的技术,就是我们通常所说的即时编译(JIT)。本文不介绍垃圾收集器Orinoco。V8的垃圾回收机制可以单独一篇文章详细介绍。下次见。参考文章V8官方文档庆祝V810周年V8是如何执行JavaScript代码的?Ignition:V8即时编译的解释器
