JavaScript引擎是执行 JavaScript 代码的程序或解释器。JavaScript引擎可以实现为标准解释器,或者以某种形式将JavaScript编译为字节码的即时编译器。
以为实现JavaScript引擎的流行项目的列表:
V8 — 开源,由 Google 开发,用 C ++ 编写
Rhino — 由 Mozilla 基金会管理,开源,完全用 Java 开发
SpiderMonkey — 是第一个支持 Netscape Navigator 的 JavaScript 引擎,目前正供 Firefox 使用
JavaScriptCore — 开源,以Nitro形式销售,由苹果为Safari开发
KJS — KDE 的引擎,最初由 Harri Porten 为 KDE 项目中的 Konqueror 网页浏览器开发
Chakra (JScript9) — Internet Explorer
Chakra (JavaScript) — Microsoft Edge
Nashorn, 作为 OpenJDK 的一部分,由 Oracle Java 语言和工具组编写
JerryScript — 物联网的轻量级引擎
为什么要创建V8引擎?
由谷歌构建的V8引擎是开源的,使用c++编写。这个引擎是在谷歌Chrome中使用的,但是,与其他引擎不同的是 V8 也用于流行的 node.js。
V8最初被设计用来提高web浏览器中JavaScript执行的性能。为了获得速度,V8 将 JavaScript 代码转换成更高效的机器码,而不是使用解释器。它通过实现 JIT (Just-In-Time) 编译器将 JavaScript 代码编译为执行时的机器码,就像许多现代 JavaScript 引擎(如SpiderMonkey或Rhino (Mozilla)) 所做的那样。这里的主要区别是 V8 不生成字节码或任何中间代码。
V8 曾有两个编译器
在 V8 的 5.9 版本出来之前,V8 引擎使用了两个编译器:
full-codegen — 一个简单和非常快的编译器,产生简单和相对较慢的机器码。
Crankshaft — 一种更复杂(Just-In-Time)的优化编译器,生成高度优化的代码。
V8 引擎也在内部使用多个线程:
主线程执行你所期望的操作:获取代码、编译代码并执行它
还有一个单独的线程用于编译,因此主线程可以在前者优化代码的同时继续执行
一个 Profiler 线程,它会告诉运行时我们花了很多时间,让 Crankshaft 可以优化它们
一些线程处理垃圾收集器
当第一次执行 JavaScript 代码时,V8 利用 full-codegen 编译器,直接将解析的 JavaScript 翻译成机器代码而不进行任何转换。这使得它可以非常快速地开始执行机器代码。请注意,V8 不使用中间字节码,从而不需要解释器。
当代码已经运行一段时间后,分析线程已经收集了足够的数据来判断应该优化哪个方法。
接下来,Crankshaft 从另一个线程开始优化。它将 JavaScript 抽象语法树转换为被称为 Hydrogen 的高级静态单分配(SSA)表示,并尝试优化 Hydrogen 图,大多数优化都是在这个级别完成的。
内联代码
第一个优化是提前内联尽可能多的代码。内联是用被调用函数的主体替换调用点(调用函数的代码行)的过程。这个简单的步骤允许下面的优化更有意义。
隐藏类
JavaScript是一种基于原型的语言:没有使用克隆过程创建类和对象。JavaScript也是一种动态编程语言,这意味着可以在实例化后轻松地在对象中添加或删除属性。
大多数 JavaScript 解释器使用类似字典的结构(基于哈希函数)来存储对象属性值在内存中的位置,这种结构使得在 JavaScript 中检索属性的值比在 Java 或 C# 等非动态编程语言中的计算成本更高。
在Java中,所有对象属性都是在编译之前由固定对象布局确定的,并且无法在运行时动态添加或删除(当然,C#具有动态类型,这是另一个主题)。
因此,属性值(或指向这些属性的指针)可以作为连续缓冲区存储在存储器中,每个缓冲区之间具有固定偏移量, 可以根据属性类型轻松确定偏移的长度,而在运行时可以更改属性类型的 JavaScript 中这是不可能的。
由于使用字典查找内存中对象属性的位置效率非常低,因此 V8 使用了不同的方法:隐藏类。隐藏类与 Java 等语言中使用的固定对象(类)的工作方式类似,只是它们是在运行时创建的。现在,让我们看看他们实际的例子:
一旦 “new Point(1,2)” 调用发生,V8 将创建一个名为 “C0” 的隐藏类。
尚未为 Point 定义属性,因此“C0”为空。