用AI赚第一桶💰低成本搭建一套AI赚钱工具,源码可二开。 广告
[TOC] # 常见的 JS解析器 JavaScript 虚拟机是一种进程虚拟机,专门设计来解释和执行的 JavaScript代码。 ![](https://img.kancloud.cn/03/9d/039d58b0098fa2dd6a3b9cc4fe88dc85_1015x534.png) # JavaScript vs C++ c++是编译型语言,在程序执行之前必须进行专门的编译过程,在生成本地代码的过程中,变量的地址和类型已经确定,运行本地代码时利用数组和位移就可以存取变量和方法的地址,不需要再进行额外的查找,几个机器指令即可完成,节省了确定类型和地址的时间,执行效率高 JavaScript是解释行语言,支持动态类型,弱类型,在程序运行的时候才进行编译,而编译前需要确定变量的类型,效率比较低,对不同系统平台有较大的兼容性(跨平台性好),需要快速地执行和解析JavaScript脚本来提高性能,V8因此而诞生 # V8引擎诞生 **v8是谷歌开源的一个基于C++语言开发的JavaScript引擎**,可以实现ECMA-262中规定的ECMAScript,其被用于谷歌浏览器chrome,安卓浏览器,node.js等大型项目当中,V8引擎可以独立运行不依赖于其他环境,也可以嵌入任何的C++应用当中使用。 # V8工作流程 V8将JavaScript代码进行编译,生成抽象语法树(AST),对作用域进行分析,分辨出局部变量或全局变量,再通过JIT技术将语法树直接转换成原生代码,没有经过字节码的编译,节省了编译时间,得到了不是最优的代码,其后通过数据分析器挑选使用频率高的代码进行优化,若优化后的效果不如之前的话就进行优化回滚。 ![抽象语法树](https://img.kancloud.cn/12/e3/12e3edd75c2d6fbef5f902ae1a203d66_693x216.png) # V8 vs JavaScriptCore JavaScriptCore是WebKit中默认的JavaScript引擎,它是苹果开源的一个项目,是苹果Safari浏览器的JavaScript引擎,应用较为广泛。 * JavaScriptCore的大致流程:JavaScript源代码 -> 抽象语法树(AST)-> 字节码-> 本地代码 * V8的大致流程:JavaScript源代码 -> 抽象语法树(AST)-> 本地代码 (2017年4月发布5.9版本后新增了Ignition字节码解释器,与JScore流程大致相同) V8引擎并不将抽象语法树转变成字节码或者其它中间表示,没有像 Java 一样的虚拟机或者字节码解释器。 > 这样做的原因? > **主要是为了减少这抽象语法树到字节码的转换时间,这一切都在网页加载时候完成,虽然可以提高优化的可能,但是这些分析可能带来巨大的时间浪费。** # V8特性 ## 两种编译器:full compiler 和 crankshaft(后来在5.9版本后被消除) * full compiler: full compiler是不含优化的编译器,目标是快速地生成原生代码,以保持页面始终快速运转,所以full compiler省去了将语法树(AST)转换为字节码的过程,直接生成原生代码。 * crankshaft: 由于full compiler没有对代码进行优化,所以V8引入了crankshaft编译器,通过数据分析器来挑选使用频率高的函数来进行优化,生成高效的原生代码,但是鉴于JavaScript是一门动态类型语言,很有可能在程序运行过程中进行类型改变,所以V8会将crankshaft编译过的代码进行优化回滚,直至回滚到当前最优状态。 ## 内联缓存(Inline caches, ICs) 由于JavaScript是一门动态类型语言,在很多操作上会相当复杂,可能一个简单的操作符都会引发起上百条指令。而V8中使用了内联缓存的机制,大致就是一个包含了对某个操作的多种实现方案的函数,在程序运行的时候动态生成并且缓存起来,方便重用,当再次访问的时候进行判断是否可以直接使用缓存结果,这样减少了很大的工作量。 ## 隐藏类 JavaScript访问对象属性的时候是通过匹配字符串的形式来查找的,而V8借鉴了C++语言中类和偏移位置的思想,实现了隐藏类,将对象按照属性是否相同划分到不同的组当中,将这些组的属性名和对应的偏移位置保存在一个隐藏类中,组内所有对象共享该信息。假如对象中新增了新的属性,那么这个对象就会被划分到一个新的隐藏类当中。 ## 垃圾回收 JavaScript使用了垃圾回收的机制,也意味着程序中是不能对内存进行管理的,这样的好处是无需程序员来额外操作内存问题,防止内存泄漏,但是坏处是无法对内存进行控制也无法对垃圾回收器进行反馈。 垃圾回收器意味着识别到需要回收的内存,将其重新分配或返还给操作系统。先看看V8的内存管理,V8将数据分为新生代,老生代和大对象区。 * 新生代:为新创建的对象分配内存空间,经常需要进行垃圾回收。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另一半在回收时负责将之前还需要保留的对象复制过来。我们只需保有一个指向内存区的指针,不断根据新对象的大小对其进行递增即可。当该指针达到了新生代区的末尾,就会有一次清理,清理掉新生代区中不活跃的死对象。 * 老生代:字符串、封箱的数字以及未封箱的双精度数字数组,在新生区存活一段时间后会被移动到这里。根据需要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收。 * 大对象区:为那些需要使用较多内存对象分配内存,当然同样可能包含数据和代码等分配的内存,一个页面只分配一个对象。垃圾回收器从不移动大对象。 在新生代中垃圾回收主要采用Scavenge算法,新生代区被划分为两个等大的子区:出区、入区。绝大多数内存的分配都会在出区发生,当出区耗尽时,我们交换出区和入区(这样所有的对象都归属在入区当中),然后将入区中活跃的对象复制至出区或老生代区当中。 在老生区中,V8在标记-清除或标记-紧缩(大周期)的过程中进行回收。大周期进行的并不频繁。一次大周期通常是在移动足够多的对象至老生区后才会发生。至于足够多到底是多少,则根据老生区自身的大小和程序的动向来定。 # V8后续发展 在2017年4月V8发布的5.9 版本中,V8团队收集了JavaScript的实测性能并仔细分析了Full-codegen的缺点和Crankshaft,新增了一个 Ignition字节码解释器,TurboFan和Ignition结合起来共同完成JavaScript的编译。关于更多TurboFan and Ignition的资料,可以参考:[https://cnodejs.org/topic/59084a9cbbaf2f3f569be482](https://cnodejs.org/topic/59084a9cbbaf2f3f569be482)和[http://benediktmeurer.de/2016/11/25/v8-behind-the-scenes-november-edition/](http://benediktmeurer.de/2016/11/25/v8-behind-the-scenes-november-edition/) # 参考 [JavaScript引擎](https://blog.csdn.net/liwenfei123/article/details/80677670) [浅读V8——强大的JavaScript引擎](https://www.jianshu.com/p/332c15fd7c7d)