JavaScript是如何实现C++性能的
今天,JavaScript已经成为网络开发中最常用的语言之一。然而,它必须通过许多障碍才能爬到这个阶段。其中一个里程碑是在它的执行速度背后,它达到了与C++等语言相似的性能。
如果没有V8 JavaScript引擎的发明,这些都是不可能的。
因此,在这篇文章中,我将讨论这些性能提升背后的技术,以及你应该了解什么来编写性能更好的代码。
什么是V8以及它是如何工作的
V8是谷歌推出的一个开源的JavaScript引擎。它是用C++编写的,支持谷歌浏览器、Chromium网络浏览器和NodeJS。它负责与环境互动并生成字节码以运行程序。
最初,V8是作为网络浏览器的性能改进机制推出的,随着时间的推移,它成为一个比其他引擎更完善的解释器。
V8与其他引擎之间最显著的区别是它的及时编译器(JIT)。
JIT编译器在运行时将所有的JavaScript编译为机器代码,不产生任何中间代码。
V8引擎的高层架构
正如你在上图中看到的,V8引擎由2个主要部分组成。第一部分负责解析你的代码将其解释为字节码,最新版本的V8使用一个名为Ignition的解释器来完成这一过程。它将把由解析器生成的**抽象语法树(AST)**作为输入,并生成字节码。
但是,我们都知道,编译器要比解释器快得多。那么,为什么V8引擎使用解释器而不是编译器?
使用Ignition解释器的主要原因是为了减少内存的使用。这是因为解释器将只编译必要的行,而不像编译器那样编译整个程序。
然而,这个Ignition解释器只负责第一次运行你的代码。然后,生成的字节码将被一个名为Turbofan的编译器使用**。**它将根据代码执行过程中收到的数据优化你的代码,并重新编译一个更优化的版本。
注意:虽然V8是用来优化JavaScript的,但它是用C++编写的,它使用多线程的方式来同时管理所有这些工作。
当我解释V8如何工作时,我提到Ignition解释器将抽象语法树作为输入,所以让我们看看什么是抽象语法树以及它如何帮助V8提高JavaScript性能。
用Bit构建和共享独立的JS组件
比特是一个可扩展的工具,可以让你用_独立_编写、版本和维护的组件创建_真正的模块化_应用 。
用它来构建模块化的应用程序和设计系统,编写和交付微服务和微前端,或者只是在应用程序之间分享组件/模块。
一个独立的源码控制和共享的 "卡 "组件(右边是它的依赖关系图,由Bit自动生成的
抽象语法树
抽象语法树是用来为编译器建立源代码的抽象结构的。而且,它并不是专门针对JavaScript或V8的;几乎每一种编程语言都使用AST来将高级代码表示转换为低级表示。
当你把你的代码转换成AST时,它将包括代码的必要细节,如变量类型、位置、语句的顺序等。因此,你的编译器将不必处理不必要的东西,如注释。
为了更好地理解,让我们拿一个简单的JavaScript代码,并为其生成AST。
// Function declaration
// Calling the function
然后我使用esprima提供的在线解析工具来为这段代码生成AST。下面的代码片段显示了AST的一部分,你可以从这里找到该代码的完整AST。
{
...
],
...
“sourceType”: “script”
AST为代码的每一行定义了键值对。最初的类型标识符定义了AST属于一个程序,然后所有的代码行将被定义在主体里面,主体是一个对象的数组。
正如我提到的,所有的函数声明、变量声明、名称、类型都是逐行组织的,而注释则被忽略了。
除了优化过程和使用AST之外,V8还使用了另一个技巧来提高JavaScript的性能。那么,让我们来看看它是什么以及它是如何工作的。
优化JavaScript代码的隐藏类
我们都知道,JavaScript是一种动态类型的语言。这意味着我们可以在飞行中添加或删除对象的属性。
即时改变对象的属性
然而,这种方法需要更多的动态查询,这就降低了JavaScript的性能。
V8引擎使用隐藏类来克服这个问题并优化JavaScript的执行。
隐藏类是如何工作的
当你创建一个新对象时,V8引擎将为其创建一个新的隐藏类。然后,如果你通过添加一个新的属性来修改同一个对象,V8引擎将创建一个新的隐藏类,其中包括前一个类的所有属性,并包括新的属性。
让我们再来看看上面的例子,看看隐藏类是如何生成的。
所以,当我创建一个空对象(const userObject = {} )时,V8将创建一个相应的没有任何偏移的隐藏类(C01)。
然后,我将通过添加一个新的属性来修改这个对象。(userObject.name = "Chameera" )。现在,V8引擎将创建一个新的隐藏类(C02),继承前一个隐藏类(C01)的所有属性,并将名称属性分配给偏移量0。
这将允许编译器在访问属性名时绕过字典查询,V8将直接指向C01类。
如果我给这个对象添加另一个属性,同样的过程会发生。另一个隐藏的类将被创建,它将有之前和新的属性作为偏移量。
这个隐藏类的概念不仅允许你绕过字典的查找;它还允许你在创建或修改类似对象时重复使用已经创建的类。
例如,如果你创建了另一个名为article的空对象(const articleObject = {}),V8引擎将不会创建一个新的隐藏类。相反,它将指向已经创建的C01类。
但是如果你修改articleObject,添加一个名为articleName的新属性,V8将无法使用之前创建的类(C02),因为它只有一个名为name的属性。
编写高性能的JavaScript代码
因此,如果你想使你的JavaScript代码的性能最大化,你可能需要减少动态属性的添加。
假设你正在NodeJS中运行一个循环。如果你为一个对象添加动态属性,你就会看到循环内部的性能差异。所以最好是在循环外创建属性并使用它们,而不是在循环内动态添加它们。
因此,当V8重用现有的隐藏类时,它的性能会好很多。
总结
每当讨论到JavaScript的工作方式时,我们都会谈论事件循环、微任务、宏任务和回调队列。然而,所有这些东西都没有在JavaScript中实现。相反,它们是V8引擎的一部分,它负责优化你的JavaScript代码。
所以,这就是为什么我想讨论V8是如何工作的,以及它使用隐藏类概念来优化你的代码的方式。
我希望你能通过这篇文章学到一些关于JavaScript的新知识,别忘了在评论区分享你的想法。
谢谢你的阅读!!!。
了解更多
揭开JavaScript性能背后的秘密。V8和隐藏类最初发表于Bitsand Pieces onMedium,在那里人们通过强调和回应这个故事来继续对话。