JavaScript 引擎
浏览器中运行 html 和 css 代码:
html 和 css 执行过程:
js 由 js 引擎(比如现在最为主流的 V8)执行。
- 高级的编程语言都是需要转成最终的机器指令来执行的;
- 事实上我们编写的JavaScript无论你交给浏览器或者Node执行,最后都是需要被CPU执行的;
- 但是CPU只认识自己的指令集,实际上是机器语言,才能被CPU所执行:
- 所以我们需要JavaScript 引擎帮助我们将JavaScript代码翻译成CPU指令来执行;
一般来说,浏览器内核包含两部分:
- WebCore:负责 html 解析,布局,渲染等工作
- JavaScriptCore:解析,执行 js 代码
在小程序中,编写的 wxml、wxss 是由渲染层执行的(WebView,直接调用原生 IOS / Android WebView 渲染),js 是由 jsCore (逻辑层)解析执行。
V8 引擎
V8是用C++编写的Google 开源高性能JavaScript和WebAssembly引藓,它用于Chrome和Node.js等 。V8 可以独立运行,也可以嵌入任何 C++ 应用程序中。
js源代码 到 ast 的过程中有词法分析(词法分析(词法分析器,也叫扫描器)将代码转换为 tokens)和语法分析(也可以称之为 parser )两个过程。
ast 到 字节码主要是 为了 后续的兼容性处理做准备,因为不同系统上的 cpu 处理机制也不同。
tokens 转换为 ast 经过 parser 和 preParser:
- Parser就是直接将tokens转成AST树架构;
- PreParser称之为预解析,为什么需要预解析呢?
- 这是因为并不是所有的 JavaScript 代码,在一开始时就会被执行。那么对所有的 JavaScript 代码进行解析,必然会影响网页的运行效率;
- 所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
- 比如我们在一个函数outer内部定义了另外一个函数inner,那么inneri函数就会进行预解析;
面试题:JavaScript代码是如何被执行的?V8引擎如何执行JavaScript代码?
- 解析(Parse):JavaScript代码首先被解析器处理,转化为抽象语法树(AST)。
这是代码编译的初步阶段,主要转换代码结构为V8内部可进一步处理的格式。
- AST:抽象语法树(AST)是源代码的树形表示,用于表示程序结构。之后,AST会被进一步编译成字节码。
- Ignition:Ignition是V8的解释器,它将AST转换为字节码。
字节码是一种低级的、比机器码更抽象的代码,它可以快速执行,但比直接的机器码慢。
- 字节码(Bytecode):字节码是介于源代码和机器码之间的中间表示,它为后续的优化和执行提供了一种更标准化的形式。
字节码是由Ignition生成,可被直接解释执行,同时也是优化编译器TurboFan的输入。
- TurboFan:TurboFan是V8的优化编译器,它接收从Ignition生成的字节码并进行进一步优化。
比如如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能。
当然还会包括很多其他的优化手段,如死代码消除(Dead Code Elimination)等,总之V8有很多手段可以提高代码执行效率。
- 机器码:经过TurboFan处理后,字节码被编译成机器码,即直接运行在计算机硬件上的低级代码。
这一步是将JavaScript代码转换成CPU可直接执行的指令,大大提高了执行速度。
- 运行时优化:在代码执行过程中,V8引擎会持续监控代码的执行情况。
如果发现之前做的优化不再有效或者有更优的执行路径,它会触发去优化(Deoptimization)。
去优化是指将已优化的代码退回到优化较少的字节码状态,然后重新编译以适应新的运行情况。
面试题:V8引擎包括哪些部分(部件),它们的作用是什么?
- Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识 JavaScript 代码;
将代码转化成AST树是一个非常常见的操作,比如在Babel、Vue源码中都需要进行这样的操作;
Parsel的V8官方文档:https:/v8.dev/blog/scanner
- Ignition是一个解释器,会将AST转换成ByteCode(字节码)
同时会收集TurboFa优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);
如果函数只调用一次,Ignition会解释执行ByteCode;
Ignition的V8官方文档:https:lv8.dev/blog/ignition-interpreter
- TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码;
比如如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFa转换成优化的机器码,提高代码的执行性能;
但是,机器码实际上也会被去优化为ByteCode(Deoptimization),这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数,原来执行的是number 类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会去优化的转换成字节码;
TurboFan的V8官方文档:https:/w8.dev/blog/urbofan-iit