V8是一个虚拟机,作为js的执行环境,它可以模拟cpu,堆栈,寄存器等
- 指令集(机器语言): cpu只能识别的二进制指令如(1000100111011000)
- 汇编指令集: 人类可识别记忆的符号(mov ax,bx)
高级语言的处理
- 解释执行(启动快,执行慢):源代码通过解析器编译成字节码,然后使用解释器解释执行字节码直接输出结果。
- 编译执行(启动慢,执行快):源代码通过解析器编译成字节码,然后通过编译器编译为机器代码,机器代码可以通过内存或者文件的形式保存,然后直接执行二进制。
V8的处理
融合解释执行和编译执行(Just In Time):准备执行环境→解析源码生成AST和作用域→生成字节码→解释字节码输出结果→监控热点代码→热点代码编译为机器码执行→反优化生成的机器码(抛弃当前优化的二进制代码,回退使用字节码解释执行)
字节码
V8早期并没有使用字节码,而是将JavaScript代码直接编译成机器码。为了提升性能,运行时将二进制存在内存中,并在浏览器退出时,缓存编译好的二进制到硬盘里。但是二进制实在是太大了,这种空间换时间的方法逐渐不可行,所以引入了字节码。
- 缓存字节码要比二进制小很多,节省空间。
- 适配CPU,统一处理
惰性解析
基于性能原因(速度慢,消耗内存),V8并不会一次性将所有的JavaScript代码全部解析为字节码。
所有主流的 JavaScript 虚拟机都实现了惰性解析。所谓惰性解析,是指解析器在执行过程中,如遇到函数声明,那么会跳过函数内部代码,而仅仅生成顶层代码的AST和字节码。
特殊情况:闭包
虽然采取了惰性解析,不会解析和执行父级函数中的子级函数,但是 V8 还是需要判断子级函数是否引用了 foo 函数中的变量,因为在正常流程中父级函数执行完毕就要销毁,这是矛盾的。负责处理这一部分的模块叫预解析器。
预解析器
预解析器的作用一个是检查函数语法错误,另一个就是检查函数内部是否引用外部变量,如果引用了,预解析器会将栈中的变量移至堆中,使用时直接使用堆中的引用。
隐藏类
V8为每个对象创建了一个隐藏类,该隐藏类保存了属性的偏移值,拿到偏移值究竟可以直接去内存中获取值。但是如果在这基础上添加或删除属性,V8会重建隐藏类。
async/await
async/await就是 Promise 和生成器应用,往底层说,就是微任务和协程应用。
协程
一个比线程颗粒度更小的任务进程,他跑在线程上,一个线程可以有多个协程,但是最多只可可以同时跑一个协程。