
文章插图
在JavaScript中,除boolean,number,string,null,undefined这个五个简单变量外,其他的数据都是对象,V8使用一种特殊的方式来表示它们,进而优化JavaScript的内部表示问题 。
在V8中,数据的内部表示由数据的实际内容和数据的句柄构成 。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针 。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的 。
除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中 。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度 。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄 。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他 。
JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性 。
2.2.工作过程前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行 。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销 。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码 。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度 。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会 。
V8引擎编译本地代码时使用的主要类如下所示:
- Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;
- Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
- AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
- AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
- FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码 。

文章插图
JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码 。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码 。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码 。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起 。
在执行编译之前,V8会构建众多全局对象并加载一些内置的库(如math库),来构建一个运行环境 。而且在JavaScript源代码中,并非所有的函数都被编译生成本地代码,而是延迟编译,在调用时才会编译 。
由于V8缺少了生成中间代码这一环节,缺少了必要的优化,为了提升性能,V8会在生成本地代码后,使用数据分析器(profiler)采集一些信息,然后根据这些数据将本地代码进行优化,生成更高效的本地代码,这是一个逐步改进的过程 。同时,当发现优化后代码的性能还不如未优化的代码,V8将退回原来的代码,也就是优化回滚 。下面介绍一下运行阶段,该阶段使用的主要类如下所示:
推荐阅读
- 认识显卡:从入门到理赔
- 大家都在用搜索引擎,你知道搜索引擎是如何找到你要的内容的吗?
- 眼影|零基础学化妆,认识化妆产品-眼影
- 4种方法实时监控Linux日志文件
- Tomcat优化大全,进来看了,真就会了
- MySQL8.0真香
- 没有人比我更懂电流,今天带你重新认识电流
- 最容易被贴罚单的9大交通标志,你认识吗?
- 分布式架构的总结
- 茶马古道的认识,真正的茶马古道是做什么的
