V8 引擎是由 Google 用 C++ 开源的 JavaScript 与 WebAssembly 引擎,目前像是 Chrome 和 Node.js 都是使用 V8 在执行 JavaScript。除了 V8 以外还有 SpiderMonkey(最早的 JavaScript 引擎,目前是 Firefox 浏览器在使用)与 JavaScriptCore(Safari 浏览器使用)等其他 JavaScript 引擎。
创新互联成都企业网站建设服务,提供网站设计、成都网站制作网站开发,网站定制,建网站,网站搭建,网站设计,自适应网站建设,网页设计师打造企业风格网站,提供周到的售前咨询和贴心的售后服务。欢迎咨询做网站需要多少钱:18982081108
好的,那麽 V8 引擎到底是如何执行 JavaScript 的呢?
V8 引擎取得 JavaScript 源代码后的第一步,就是让 Parser 使用 Scanner 提供的 Tokens(Tokens 裡有 JavaScript 内的语法关键字,像是 function、async、if 等),将 JavaScript 的原始码解析成** abstract syntax tree**,就是大家常在相关文章中看到的 AST(抽象语法树)。
如果好奇 AST 长什麽样子的话,可以使用 acron 这个 JavaScript Parser,或是 这个网站 生成 AST 参考看看。以下是使用 acron 的代码:
const { Parser } = require('acorn')
const javascriptCode = `
let name;
name = 'Clark';
`;
const ast = Parser.parse(javascriptCode, { ecmaVersion: 2020 });
console.log(JSON.stringify(ast));
下方是解析 let name; name = 'Clark'; 所得到的 AST:
{
"type": "Program",
"start": 0,
"end": 31,
"body": [
{
"type": "VariableDeclaration",
"start": 3,
"end": 12,
"declarations": [
{
"type": "VariableDeclarator",
"start": 7,
"end": 11,
"id": {
"type": "Identifier",
"start": 7,
"end": 11,
"name": "name"
},
"init": null
}
],
"kind": "let"
},
{
"type": "ExpressionStatement",
"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 可以从上到下,由左而右去理解它在执行的步骤:
产生 AST 后,就完成了 V8 引擎的第一个步骤。
JIT 的中文名称是即时编译,这也是 V8 引擎所採用在执行时编译 JavaScript 的方式。
将代码转变为可执行的语言有几种方法,第一种是编译语言,像是 C/C++ 在写完代码的时候,会先经过编译器将代码变成机器码才能执行。第二种就像 JavaScript,会在执行的时候将代码解释成机器懂的语言,一边解释边执行的这种,称作直译语言。
编译语言的好处是可以在执行前的编译阶段,审视所有的代码,将可以做的优化都完成,但直译语言就无法做到这一点,因为执行时才开始解释的关係,执行上就相对较慢,也没办法在一开始做优化,为了处理这个状况,JIT 出现了。
JIT 结合解释和编译两者,让执行 JavaScript 的时候,能够分析代码执行过程的情报,并在取得足够情报时,将相关的代码再编译成效能更快的机器码。
听起来 JIT 超讚,而在 V8 引擎裡负责处理 JIT 的左右手分别为 Ignition 和 **TurboFan**。
成功解析出 AST 后,Ignition 会将 AST 解释为 ByteCode,成为可执行的语言,但是 V8 引擎还未在这裡结束,Ignition 用 ByteCode 执行的时候,会搜集代码在执行时的类型信息。举个例子,如果我们有个 sum 函式,并且始终确定呼叫的参数类型都是 number,那麽 Ignition 会将它记录起来。
此时,在另一方面的 TurboFan 就会去查看这些信息,当它确认到“只有 number 类型的参数会被送进 sum 这个函式执行”这个情报的时候,就会进行 Optimization,把 sum 从 ByteCode 再编译为更快的机器码执行。
如此一来,就能够保留 JavaScript 直译语言的特性,又能够在执行的时候优化性能。
但毕竟是 JavaScript,谁也不敢保证第一百万零一次送进来的参数仍然是 number,因此当 sum 接收到的参数与之前 Optimization 的策略不同时,就会进行 Deoptimization 的动作。
TurboFan 的 Optimization 并不是将原有的 ByteCode 直接变成机器码,而是在产生机器码的同时,增加一个 Checkpoint 到 ByteCode 和机器码之间,在执行机器码之前,会先用 Checkpoint 检查是否与先前 Optimization 的类型符合。这样的话,当 sum 以与 Optimization 不同的类型被呼叫的时候,就会在 Checkpoint 这关被挡下来,并进行 Deoptimization。
最后如果 TurboFan 重複执行了 5 次 Optimization 和 Deoptimization 的过程,就会直接放弃治疗,不会再帮这个函式做 Optimization。
那到底该怎麽知道 TurboFan 有没有真的做 Optimization 咧?我们可以用下方的代码来做个实验:
const loopCount = 10000000;
const sum = (a, b) => a + b;
performance.mark('first_start');
for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}
performance.mark('first_end');
performance.mark('second_start');
for (let i = 0; i < loopCount; i += 1) {
sum(1, i);
}
performance.mark('second_end');
performance.measure('first_measure', 'first_start', 'first_end');
const first_measures = performance.getEntriesByName('first_measure');
console.log(first_measures[0]);
performance.measure('second_measure', 'second_start', 'second_end');
const second_measures = performance.getEntriesByName('second_measure');
console.log(second_measures[0]);
上方利用 Node.js v18.1 的 perf_hooks 做执行速度的测量,执行结果如下:
执行后会发现第一次执行的时间花了 8 秒,第二次的执行时间只花了 6 秒,大家可以再把 loopCount 的数字改大一点,差距会越来越明显。
但是这麽做仍然没办法确认是 TurboFan 动了手脚,因此接下来执行的时候,加上 --trace-opt 的 flag,看看 Optimization 是否有发生:
执行后的信息显示了 TurboFan 做的几次 Optimization,也有把每次 Optimization 的原因写下来,像第一二行分别显示了原因为 hot and stable 和 small function,这些都是 TurboFan 背后做的 Optimization 策略。
那 Deoptimization 的部分呢?要测试也很简单,只要把第二个迴圈的参数型别改成 String 送给 sum 函式执行,那 TurboFan 就会进行 Deoptimization,为了查看 Deoptimization 的讯息,下方执行的时候再加上 --trace-deopt:
在 highlight 的那一段,就是因为送入 sum 的参数型别不同,所以执行了 Deoptimization,但是接下来又因为一直送 String 进 sum 执行,所以 TurboFan 又会再替 sum 重新做 Optimization。
整理 V8 引擎执行 JavaScript 的过程后,能够得出下方的流程图:
搭配上图解说 V8 引擎如何执行 JavaScript:
作者:神Q超人 > 来源:medium
原文:https://medium.com/tarbugs/%E5%9F%B7%E8%A1%8C-javascript-%E7%9A%84-v8-%E5%BC%95%E6%93%8E%E5%81%9A%E4%BA%86%E4%BB%80%E9%BA%BC-f97e5b4b3fbe
作者:Andy Chen
译者:前端小智
来源:medium
原文:https://medium.com/starbugs/%E5%8E%9F%E4%BE%86%E7%A8%8B%E5%BC%8F%E7%A2%BC%E6%89%93%E5%8C%85%E4%B9%9F%E6%9C%89%E9%80%99%E9%BA%BC%E5%A4%9A%E7%9C%89%E8%A7%92-%E6%B7%BA%E8%AB%87-tree-shaking-%E6%A9%9F%E5%88%B6-8375d35d87b2
网页标题:面试写:说说执行JavaScript的V8引擎做了什么?
文章起源:http://www.shufengxianlan.com/qtweb/news39/131439.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联