关于对象的访问优化
先划重点:要拥有最高的性能,尽量让对象处于快速模式
一切的一切先从一段 benchmark 开始说起:
setup block:
function createObjects() {
return [
{x: 1, y: 2, z: 3},
{a: 1, b: 2, c: 3}
];
}
function test(obj) {
var sum = 0;
for (var i = 0; i < 100; i++) {
sum += obj.a + obj.c;
}
return sum;
}
case 1:pair1 没有 delete 操作
var pair = createObjects();
delete pair[0].y;
test(pair[1]);
case 2:pair1 存在 delete 操作
var pair = createObjects();
delete pair[1].b;
test(pair[1]);

通过测试我们可以发现没有 delete 操作的比存在 delete 操作的对象访问速度快 3.4x 左右。
要解释这个现象,我们就要先了解 V8 对于 JavaScript 对象的两种访问模式:
- Dictionary(Slow) Mode:字典模式也称为哈希表模式,V8 使用哈希表来存储对象的属性。
- Stable(Fast) Mode:使用类似数组(C Struct)结构来存储对象的属性并使用 Offset 进行访问。
新创建的小对象为快速模式(Fast Mode),当执行如下操作时会退化成为字典模式(Dictionary Mode):
- 动态添加过多的属性
- 删除属性(delete)
- 删除非最后添加的属性(V8 >= 6.0)
也就是说当对象被当作哈希表使用时(如存储大量数据),他就会退化到字典模式。
在开发调试过程中,可以调用 V8 的 RuntimeCall (开启 -allow-natives-syntax)来判断与优化对象当前的状态:
- %HasFastProperties(Object): 判断对象当前是否处于快速模式下。
- %ToFastProperties(Object):强制优化对象到快速模式。
我们来看个例子:
// flags: --allow-natives-syntax
var obj = {x : 1, y : 2};
%HasFastProperties(obj); // true : 新对象为快速模式
delete obj.x;
%HasFastProperties(obj); // false : 删除(非最后一个添加的)属性退化为字典模式
%ToFastProperties(obj);
%HasFastProperties(obj); // true : 调用 RuntimeCall 强制优化为快速模式
for (var i = 0; i < 100; i++) {
obj['arg' + i] = i;
}
%HasFastProperties(obj); // false : 动态添加过多的属性退化为字典模式
如上文所述,在开发环境中可以调用 RuntimeCall(%ToFastProperties)将处于字典模式的对象优化成为快速(Fast)模式。
而在运营环境中,当对象被设置成为一个函数(或对象)的原型时也会从字典模式优化成为快速(Fast) 模式。
function MagicFunc(obj) {
function FakeConstructor() {
this.x = 0;
}
FakeConstructor.prototype = obj;
new FakeConstructor();
new FakeConstructor();
};
// flags: --allow-natives-syntax
var obj = {x : 1, y : 2};
delete obj.x;
%HasFastProperties(obj);
// false : 删除(非最后添加的)属性,退化到字典模式
MagicFunc(obj);
%HasFastProperties(obj);
// true : !magic!
调用 MagicFunc 后对象由字典模式优化为快速模式,这不是魔法!
参考: