V8引擎中对于JS对象的实现

V8引擎中对于JS对象的实现

JS对象的本身语法以及V8引擎对这些语法的支持和优化,JS对象的定义,对象的访问和存储,对象的复用,对象的继承。

首先对象的概念,对象是一个包含相关数据和方法的集合(通常由一些变量和函数组成,这些变量称作属性,函数就称作方法)。而对于对象这种集合的访问方法,一般都是通过访问它们的key值的方式,在JS语言中就是通过点方式法来访问对象的属性和方法,此时对象的名字就是一个命名空间,而点之后紧接的是一个标识符,这个标识符一般为属性的名字。而命名空间是可以支持链式调用的,也就是父命名空间后可以定义子命名空间,通过多个点符号来访问内部的命名空间。而另外一种访问方式,则是通过括号表示法,括号里面的内容是表示属性名的字符串,也可以是一个表达式方式来访问(ES6新增语法),可以支持动态属性名。

由于JS对象是可以支持动态设置属性,而在程序中对于对象的读取的频率是很高的,而由于对象的属性是存储在堆中,每次都需要去查找,因此V8引擎根据这种情况,通过快属性和慢属性的存储策略来提高对象属性的查找效率。

其次是对象的存储,由于V8引擎中,对象是存储在其管理下的堆内存中,在栈中存储的是表示对应堆内存的内存地址,也就是指针。在V8中,对象主要是由三个指针构成,分别是隐藏类,Property和Element组成的。如下图所示:

 

V8引擎中对于JS对象的实现
V8D对象存储结构

 

其中,隐藏类是用来描述对象的结构,Property和Element是用于存放对象的属性,它们主要的区别体现在是否能被索引。

在对象的复用方面,由于JS语言是基于对象来设计的,但并不是严格意义上的OOP设计的语言。而要做到对象的复用,则需要通过继承的方式来达到复用的策略,不同于java语言通过基于类实现继承,js语言是通过基于原型的方式来实现继承。因为继承本质上一个对象可以访问另外一个对象中的属性和方法,因此js语言是通过在对象中引入一个叫做原型的属性,来实现语言的继承机制。如下图所示

V8引擎中对于JS对象的实现
v8原型示例

通过在每个对象里包含一个隐藏属性__proto__,这个隐藏属性是一个指针,指向另外一个对象。比如上图中的B对象的__proto__属性就指向了对象A,那么A就是B的原型对象,而对象D的__proto__就指向对象B,这样就构造一个对应关系,可以服用原型对象的属性和方法,来达到复用的目的。这个查找属性和方法的路径,就称作为原型链。但是,由于之前ECMA规范中制定了class关键字,来模拟类式继承的写法,而这实际上就是语法糖,通过封装一些对象的原型方式来做到的,如下图所示:

function Dog(type, color) { this.type = type; this.color = color; } var dog = new Dog('Dog', 'black')

转换为实际执行的前端代码为:

var dog = {} //定义一个空对象 dog.__proto__ = Dog.prototype; //通过prototype来对接其他对象的继承关系 Dog.call(dog, 'Dog', 'black') //跟js语言的静态作用域有关,需要更改其内部this值

以下为实际的程序执行流程图:

V8引擎中对于JS对象的实现 

后续思考:

  • 对象的prototype和__proto__属性有什么异同点?
  • 在实现原型的继承中,contructor属性的作用?

 

[参考资料]

极客时间《图解V8》