持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
前言
之前在重学JavaScript | 浏览器的渲染及解析 - 掘金 (juejin.cn)说到过当浏览器遇到js代码的时候,它是交由javaScript引擎来解析和运行的,身为一名前端,自然要了解下这个神秘的js引擎
JavaScript引擎
为什么我们执行代码要用到javaScript引擎呢
- 重学JavaScript | 重新认识计算机语言 - 掘金 (juejin.cn) 之前的这篇文章里说到,高级编程语言的计算机是不认识的,都需要转换成对应的
机器指令才可以 - 我们编写的javaScript代码,不管是在
浏览器或者node中执行都是最终要被cpu执行的 - cpu只能识别
机器语言,也就是自己的指令集,转换为这些才能被cpu执行
综上所属,我们需要一个JavaScript引擎来将我们写的js代码转换为cpu指令来执行,此外,还负责执行代码、分配内存以及垃圾回收等
常见的javaScript引擎
简单了解一下常见的js引擎
- SpiderMonkey: 是第一个js引擎,由js之父Brend Eich开发
- JavaScriptCore: 是apple公司开发的一款引擎,主要用于wkit渲染引擎的浏览器
- Charkra: 微软开发的用于ie浏览器的js引擎
- V8: 号称目前最强大
JavaScript引擎,由Google开发,广泛应用于目前绝大多数浏览器
js引擎和浏览器内核的关系
以webkit为例,webkit事实上是由两部分组成的
webCore:负责HTML代码的解析,布局,渲染等工作JavaScriptCore: 负责解析和执行js代码
如下图
再来看一下微信小程序的例子
这个渲染层和逻辑层是不是就是webkit的两块,实际上,小程序的js代码就是由JavaScriptCore来负责执行的
相信现在你已经了浏览器内核和javaScript引擎的关系了,接下来让我们仔细介绍下我们的今天的主角,强大的V8引擎
强大的V8引擎
先来看下v8官方是怎么描述它的
V8是Google的开源高性能JavaScript和WebAssembly引擎,用C++编写。它用于Chrome和Node.js等。
它实现了 ECMAScript 和 WebAssembly,并在 Windows 7 或更高版本、macOS 10.12+ 以及使用 x64、IA-32、ARM 或 MIPS 处理器的 Linux 系统上运行
V8 可以独立运行,也可以嵌入到任何C++应用程序中。
V8引擎的基本原理
来看一张图,这是V8引擎解析js代码的全过程
大家一定很疑惑吧,图上的parse和ignition以及Turbofan分别代表什么呢?
V8引擎本身的源码结构非常复杂,大概有超过100w行C++代码,通过了解它的架构,我们可以知道它是如何对JavaScript执行的:
Parse模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不直接认识JavaScript代码;如果函数没有被调用,那么是不会被转换成AST的Ignition是一个解释器,会将AST转换成ByteCode(字节码),同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类型才能进行真实的运算);TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码; 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能- 但是,当优化的代码的变量类型发生变化时,字节码还是会转换成机械码的,比如如下的代码
function sum(a,b) {
return a+b
}
sum(1,2) // Number类型的数据
sum(2,3) // Number类型的数据
sum(2,2) // Number类型的数据
// 我们这个sum函数的参数一直是两个Number类型的数据相加,这个函数就会被标记为热点函数,就会经过`TurboFan`转换成优化的机器码
sum(2,'777') // Number类型的数据和字符串相加
// 但是当string类型和 Number类型相加时,之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码
我们再来分析下V8引擎的基本原理里的流程图
- parse对js代码进行词法解析和语法解析,最终转换为AST抽象语法树
// 这里是代码块2:
let name = 'juejin'
// 1.进行词法解析,提取let,name,=,'juejin'
// 上述代码的tokens
tokens = [ { type:"keyword", value: let, } { type:"identifier", value: "name", } ... ]
// 2.再根据tokens来进行语法分析,比如type,value分别是什么,然后转换为AST语法树
// 3. let name = 'juejin' 对应的JSON
{
"type": "Program",
"start": 0,
"end": 19,
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 19,
"declarations": [
{
"type": "VariableDeclarator",
"start": 4,
"end": 19,
"id": {
"type": "Identifier",
"start": 4,
"end": 8,
"name": "name"
},
"init": {
"type": "Literal",
"start": 11,
"end": 19,
"value": "juejin",
"raw": "'juejin'"
}
}
],
"kind": "let"
}
],
"sourceType": "module"
}
// 4. let name = 'juejin' 对应的AST树 看下图
可通过AST explorer来进行js代码和ast树的转换
Ignition将AST转换成ByteCode(字节码),代码的运行环境是不一定的,而不同的环境他有不同的CPU架构,他们执行的机械指令是不一样的,而Ignition他不知道该转换成什么机械指令的代码,而ByteCode(字节码)他相当于是跨平台的,v8引擎就会把字节码转换成对应的汇编代码然后再转换成相应平台(window、Linux)的CPU指令(0101)- 中途会遇到多次执行的代码,通过
TurboFan进行收集和优化
V8引擎的执行细节
先来看一张官方的执行图
可以看出来官方的图上多了parser解析前的过程,我们接下来来解释下这部分的过程
我们浏览器遇到JS代码后,V8引擎是怎么做的
Blink(内核)对HTML代码进行解析,遇到js代码后将源码交给V8引擎Stream(数据流)获取源码并进行编码转换Scanner(扫描器)Scanner会进行词法分析(lexicalanalysis),词法分析会将代码转换成tokens;- tokens会被转换成AST树,经过
Parser和PreParser,Parser就是直接将tokens转成AST树架构;
没错,3,4就是代码块2里描述的过程
Parser和PreParser(解析和预解析)
JavaScript代码在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会影响网页的运行效率,所以V8引擎就实现了LazyParsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析 也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
举个栗子
// 代码块3
function f() {
function c() {
}
}
// 例如函数f中定义了一个函数c,这个时候就不解析函数c,只会对它进行预解析