有梦想,有干货,微信搜索【大千世界】关注这位凌晨还在洗碗的洗碗智者。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。你好!大家好,我想点进来的大家应该都听说过,在浏览器或者Node.js上执行JavaScript,但是大家有没有想过JavaScript是怎么执行的呢?这背后的功臣就是JavaScript引擎,而标题中提到的V8引擎也是其中之一!V8引擎是由Google在C++中开源的JavaScript和WebAssembly引擎。目前,Chrome和Node.js都使用V8来执行JavaScript。除了V8之外,还有SpiderMonkey(最早的JavaScript引擎,目前Firefox浏览器使用)、JavaScriptCore(Safari浏览器使用)等其他JavaScript引擎。好的,那么V8引擎到底是如何执行JavaScript的呢?V8引擎执行过程ScannerV8引擎获取JavaScript源码后的第一步是让Parser使用Scanner提供的Tokens(Tokens中有JavaScript中的语法关键字,如function、async、if等),将JavaScript源码被解析成一棵抽象语法树,也就是大家经常在相关文章中看到的AST(抽象语法树)。如果你对AST长什么样子很好奇,可以使用acron、JavaScriptParser或本网站生成AST以供参考。下面是使用acron的代码:const{Parser}=require('acorn')constjavascriptCode=`letname;名字='克拉克';`;constast=Parser.parse(javascriptCode,{ecmaVersion:2020});控制台.log(JSON.stringify(ast));下面是解析letname得到的AST;name='Clark';:{"type":"Program","start":0,"end":31,"body":[{"type":"VariableDeclaration","start":3,"end":12,"声明":[{"type":"VariableDeclarator","start":7,"end":11,"id":{"type":"Identifier","start":7,"end":11,"name":"name"},"init":null}],"kind":"let"},{"type":"ExexpressionStatement","start":15,"end":30,"expression":{"type":"AssignmentExpression","start":15,"end":29,"operator":"=","left":{"type":"Identifier","start":15,"end":19,"name":"name"},"right":{"type":"Literal","start":22,"end":29,"value":"Clark","raw":"'Clark'"}}}],"sourceType":"script"}如果我们更进一步,将上面的AST转换成图表,它会看起来像这样:AST可以从上到下和从左到右理解它正在执行的步骤:到VariableDeclaration创建一个名为name的变量到ExpressionStatement到expression到AssignmentExpression,遇到=,和左边是name,右边是stringClark生成AST之后,V8引擎的第一步就完成了JIT(Just-In-Time)JIT的中文名称是即时编译,这也是V8引擎在执行时编译JavaScript的方式。有几种方法可以将代码转换为可执行语言。第一种是编译型语言,比如C/C++。编写代码时,代码在执行前会被编译器转换成机器码。二是像JavaScript,在执行时将代码解释成机器能理解的语言,称为边解释边执行的直译语言。编译型语言的优点是可以在执行前检查编译阶段的所有代码,完成所有可以做的优化,但是直译语言做不到这一点,因为解释和执行之间的关系比较慢在执行中。慢,一开始也没办法优化。为了应对这种情况,JIT出现了。JIT结合了解释和编译,使得在JavaScript执行时,可以分析代码执行过程的信息,当获取到足够的信息时,可以重新编译相关代码,从而产生更快的机器码。听起来JIT牛逼,V8引擎中负责JIT的左右手分别是Ignition和TurboFan。Ignition&TurboFan成功解析AST后,Ignition会将AST解释成ByteCode,成为一种可执行的语言,但V8引擎到这里还没有结束。当Ignition使用ByteCode执行时,它会在执行过程中收集代码的类型信息。例如,如果我们有一个sum函数,并且始终确保调用的参数类型是数字,那么Ignition会记录它。这时,另一边的TurboFan会检查这些信息,当它确认“只有number类型的参数会被发送到sum函数执行”的信息时,它就会进行优化,将sum从ByteCode转换为Then编译以更快地执行机器代码。这样既可以保留JavaScript直译语言的特性,又可以在执行过程中优化性能。但是毕竟是JavaScript,谁也不能保证第100万次第一次传进来的参数还是一个数字。因此,当sum接收到的参数与之前的Optimization策略不同时,就会执行反优化动作。TurboFan的Optimization并不是直接将原始的ByteCode转为机器码,而是在生成机器码的同时,在ByteCode和机器码之间增加了一个Checkpoint。在执行机器码之前,它会检查是否与之前的优化类型一致。这样,当调用sum的类型不同于Optimization时,它将被阻塞在Checkpoint并执行Deoptimization。最后,如果TurboFan重复优化和反优化过程5次,会直接放弃处理,不会帮助这个函数优化。那么如何知道TurboFan是否真的进行了优化呢?我们可以用下面的代码做一个实验:constloopCount=10000000;constsum=(a,b)=>a+b;performance.mark('first_start');for(leti=0;i
