在 JavaScript 中,对象就像一个字典,可以使用字符串作为键名,任意对象作为键值。早期的实现方式是使用字典来存储对象的属性。
字典是非线性的数据结构,查询效率会低于线性的数据结构,为了提高存储和查找的效率,V8 采用了一套复杂的存储策略。它将对象的属性分为常规属性(properties)和排序属性(elements)。
function Foo() {
this[100] = 'test-100'
this[1] = 'test-1'
this["B"] = 'bar-B'
this[50] = 'test-50'
this[9] = 'test-9'
this[8] = 'test-8'
this[3] = 'test-3'
this[5] = 'test-5'
this["A"] = 'bar-A'
this["C"] = 'bar-C'
}
var bar = new Foo()
for(key in bar){
console.log(`index:${key} value:${bar[key]}`)
}
打印出来的属性顺序并不是我们设置的顺序
之所以出现这样的结果,是因为在 ECMAScript 规范中定义了数字属性应该按照索引值大小升序排列,字符串属性根据创建时的顺序升序排列。
在这里我们把对象中的数字属性称为排序属性,在 V8 中被称为 elements,字符串属性就被称为常规属性,在 V8 中被称为 properties。
??将不同的属性分别保存到 elements 属性和 properties 属性中,无疑简化了程序的复杂度,但是在查找元素时,却多了一步操作,比如执行 bar.B这个语句来查找 B 的属性值,那么在 V8 会先查找出 properties 属性所指向的对象 properties,然后再在 properties 对象中查找 B 属性,这种方式在查找过程中增加了一步操作,因此会影响到元素的查找效率。
??为了进一步提高查找效率,V8 采用了对象内属性(in-object properties)的策略。对象内属性是指将部分常规属性直接存储在对象本身中,而不是在单独的属性存储容器中。这样,在查找属性时,V8 可以直接从对象本身获取属性的值,而无需额外的查找步骤。
??采用对象内属性之后,常规属性就被保存到 bar 对象本身了,这样当再次使用bar.B来查找 B 的属性值时,V8 就可以直接从 bar 对象本身去获取该值就可以了,这种方式减少查找属性值的步骤,增加了查找效率。
??然而,对象内属性的数量是固定的,默认为10个。如果对象中的属性超出了对象内属性的空间,那么这些属性将被存储在常规属性存储容器中。虽然常规属性存储多了一层间接层,但它们可以自由地扩容。
??通常,保存在线性数据结构中的属性被称为快属性,因为可以通过索引快速访问属性。然而,如果添加或删除大量属性,线性数据结构的性能会受到影响。为此,当对象的属性过多时,V8 采用了慢属性的存储策略。慢属性使用非线性数据结构(词典)作为属性存储容器,所有属性的元信息都直接保存在属性字典中。
总结:
??V8 通过快属性和慢属性的存储策略,提高了对象属性的访问速度。快属性直接存储在对象内部,快速访问效率高。而慢属性则使用属性字典存储,适用于属性数量过多或频繁添加和删除属性的情况,以提高修改属性的效率。
???通过引入这两个属性,加速了 V8 查找属性的速度,为了更加进一步提升查找效率,V8 还实现了内置内属性的策略,当常规属性少于一定数量时,V8 就会将这些常规属性直接写进对象中,这样又节省了一个中间步骤。
??但是如果对象中的属性过多时,或者存在反复添加或者删除属性的操作,那么 V8 就会将线性的存储模式降级为非线性的字典存储模式,这样虽然降低了查找速度,但是却提升了修改对象的属性的速度。
?