隐藏类 - ParadiseGuo/JavascriptLearning GitHub Wiki
JavaScript 是一种基于原型的语言:没有类和对象而是使用克隆创建的。 JavaScript 也是一种动态编程语言,这意味着属性可以在实例化后方便地添加或从对象中移除。
大多数 JavaScript 解释器使用类似字典的结构(基于散列函数)来存储对象属性值在内存中的位置。这种结构使得在 JavaScript 中检索一个属性的值比在 Java 这样的非动态编程语言中的计算量要大得多。在 Java 中,所有的对象属性都是在编译之前由一个固定的对象决定的,并且不能在运行时动态添加或删除。因此,属性的值(或指向这些属性的指针)可以作为连续的缓冲区存储在内存中,每个值之间有一个固定的偏移量。偏移量的长度可以很容易地根据属性类型来确定,而在运行时属性类型可以改变的 JavaScript 中这是不可能的。
由于使用字典查找内存中对象属性的位置效率非常低,因此 V8 使用了不同的方法:隐藏类。隐藏类与 Java 等语言中使用的固定对象(类)的工作方式类似,除了隐藏类是在运行时创建的这点区别。现在,让我们看看他们实际的例子
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
一旦 “new Point(1,2)” 调用发生,V8 将创建一个名为 “C0” 的隐藏类。
尚未为 Point 定义属性,因此“C0”为空。
一旦第一个语句 “this.x = x” 被执行(在 “Point” 函数内部),V8 将创建第二个隐藏的类,名为“C1”,它基于“C0”。 “C1”描述了可以找到属性x的在内存中的位置(相对于对象指针)。在这种情况下,“x”被存储在0处,这意味着当在内存中将点对象看作一段连续存储空间时,第一个地址将对应于属性“x”。 V8 也会用“class transition”来更新“C0”,如果一个属性“x”被添加到一个点对象时,隐藏类应该从“C0”切换到“C1”。下面的点对象的隐藏类现在是“C1”。
每当一个新的属性被添加到一个对象中时,旧的隐藏类对象将被更新为一个到新隐藏类的转换路径。 隐藏类转换非常重要,因为它们允许隐藏类在以相同方式创建的对象之间共享。 如果两个对象共享一个隐藏类,并将相同的属性添加到这两个对象,则转换可确保两个对象都接收到相同的新隐藏类以及随附的所有优化代码。
当语句 “this.y = y” 被执行时,会重复同样的过程(在 “Point” 函数内部,“this.x = x”语句之后)。
一个名为“C2”的新隐藏类会被创建,如果将一个属性 “y” 添加到一个 Point 对象(已经包含属性“x”),一个类转换会添加到“C1”,则隐藏类应该更改为“C2”,点对象的隐藏类更新为“C2”。
隐藏类转换取决于将属性添加到对象的顺序。看看下面的代码片段:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
p1.a = 5;
p1.b = 6;
var p2 = new Point(3, 4);
p2.b = 7;
p2.a = 8;
现在,假设对于p1和p2,将使用相同的隐藏类和转换。那么,对于“p1”,首先添加属性“a”,然后添加属性“b”。然而,“p2”首先分配“b”,然后是“a”。因此,由于不同的转换路径,“p1”和“p2”以不同的隐藏类别结束。在这种情况下,以相同的顺序初始化动态属性好得多,以便隐藏的类可以被重用。