JavaScript的快慢属性
在V8中,对象主要由三个指针构成,分别是隐藏类(Hidden Class),Property
还有 element

其中,隐藏类用于描述对象的结构。Property
和 Element
用于存放对象的属性,它们的区别主要体现在键名能否被索引。
Properties 与 Element
在 V8 内部,为了有效地提升存储和访问这两种属性的性能,分别使用了两个线性数据结构Properties、Element来分别保存排序属性和常规属性
// 可索引属性会被存储到 Elements 指针指向的区域
{ 1: "a", 2: "b" }
// 命名属性会被存储到 Properties 指针指向的区域
{ "first": 1, "second": 2 }
这是为了满足
var a = { 1: "a", 2: "b", "first": 1, 3: "c", "second": 2 }
var b = { "second": 2, 1: "a", 3: "c", 2: "b", "first": 1 }
console.log(a)
// { 1: "a", 2: "b", 3: "c", first: 1, second: 2 }
console.log(b)
// { 1: "a", 2: "b", 3: "c", second: 2, first: 1 }
可以发现b的对象顺序和声明的不太一样。
可以看到索引属性【integer-indexed】(如1,2)按照升序排列。
而命名属性【named properties】(如first,second)则是按照声明顺序排列。
在同时使用索引属性和命名属性时可以看到两者发生了分隔。

将属性分解成这两种线性数据结构之后,如果执行索引操作,那么 V8 会先从 elements 属性中按照顺序读取所有的元素,然后再在 properties 属性中读取所有的元素,这样就完成一次索引操作。
命名属性的不同存储方式
通过上述,我们知道V8将不同属性保存在elements属性和properties属性中,这样简化了程序复杂度,但是查找元素时却多了一步操作,比如bar.B,V8会先找出properties属性所指向对象,然后在properties对象上查找B属性,这种方式增加一步操作,就会影响到元素的查找效率。
基于这个原因,V8使用一种权衡策略用以提高查找属性效率。这个策略就是将部分常规属性存储到对象本身,这种称为对象内属性(in-object-properties)
V8 中命名属性有三种的不同存储方式:对象内属性(in-object)、快属性(fast)和慢属性(slow)。
用例子来说明对象内属性、快属性和慢属性
function foo() {}
let a = new foo()
let b = new foo()
let c = new foo()
for(let i = 0; i<10; i++){
a[new Array(i+2).join('a')] = 'aaa'
}
for(let i = 0; i<12; i++){
b[new Array(i+2).join('b')] = 'bbb'
}
for(let i = 0; i<30; i++){
c[new Array(i+2).join('c')] = 'ccc'
}
a、b、c 三个对象分别有10、12、30个属性,分别会以对象内属性,对象内属性+快属性、对象内属性+慢属性这三种方式存储。
通过控制台执行上述脚本,在控制台的Memory内存处,获取内存快照,并在筛选框搜索foo。

a - 对象内属性

b - 对象内属性+快属性

c - 对象内属性+慢属性

结论与启示
- 属性分为命名属性和可索引属性,命名属性存放在
Properties
中,可索引属性存放在Elements
中。 - 命名属性有三种不同的存储方式:对象内属性、快属性和慢属性,前两者通过线性查找进行访问,慢属性通过哈希存储的方式进行访问。