V8引擎中PlainObject、ES6 Class与传统Class性能差异探究
V8优化器下ES6类字段声明的性能差异问题
测试场景与结果
为优化CPU密集型JavaScript代码,针对矩阵组合场景做基准测试,用三种方式存储6个矩阵值(Xx、Xy、Yx、Yy、Tx、Ty):
1. 普通对象(PlainObject)
XfObj.new = (Xx, Xy, Yx, Yy, Tx, Ty) => { return { Xx, Xy, Yx, Yy, Tx, Ty }; };
2. 带字段声明的ES6 Class
class XfCls_ { Xx; Xy; Yx; Yy; Tx; Ty; constructor(Xx, Xy, Yx, Yy, Tx, Ty) { this.Xx = Xx; this.Xy = Xy; this.Yx = Yx; this.Yy = Yy; this.Tx = Tx; this.Ty = Ty; } } XfCls.new = (Xx, Xy, Yx, Yy, Tx, Ty) => { return new XfCls_(Xx, Xy, Yx, Yy, Tx, Ty); };
3. 传统构造函数式Class
const XfCls2_ = function XfCls2_(Xx, Xy, Yx, Yy, Tx, Ty) { this.Xx = Xx; this.Xy = Xy; this.Yx = Yx; this.Yy = Yy; this.Tx = Tx; this.Ty = Ty; return this; }; XfCls2.new = (Xx, Xy, Yx, Yy, Tx, Ty) => { return new XfCls2_(Xx, Xy, Yx, Yy, Tx, Ty); };
性能测试结果(仅迭代处理大量矩阵引用数组,初始化/预热不计入)
- 普通对象:3569 ms
- ES6 Class(带字段声明):13577 ms
- 传统Class:2519 ms
关键发现:移除ES6 Class的字段声明后,性能与传统Class一致,差异源于字段声明。同时用v8-natives检测发现三者的Map不同,但debugPrint结果极为相似。
问题解答
1. 为何ES6类字段声明会导致实例性能下降?
V8对带字段声明的ES6类实例的内存布局处理存在细微差异:
- 传统构造函数和普通对象初始化时,直接给实例添加属性,V8会将这些属性标记为**“own properties”**,并优化为紧凑的内联存储(inline properties),访问时可直接通过偏移量读取,速度极快。
- 带字段声明的ES6类,字段会被视为类的**“public instance fields”**,在类定义阶段就会注册到类的
prototype关联Map中。实例初始化时,V8需要先验证字段存在性,处理字段初始化逻辑(即使构造函数直接赋值),这会引入额外检查步骤,拉长属性访问路径,在CPU密集型循环中被放大,最终体现为性能下降。 - 此外,带字段声明的类实例Map包含字段元信息(如可写性、枚举性),内部属性查找逻辑与普通对象/传统构造函数不同,V8优化器无法完全复用最优访问策略。
2. 为何三者Map不同但debugPrint结果相似?
V8中的Map(又称Hidden Class)是描述对象结构的元数据,不同创建方式会生成不同Map:
- 普通对象的Map基于字面量创建,初始结构就是包含6个属性的PlainObject Map。
- 传统构造函数实例的Map是在构造函数执行中逐步添加属性生成的,最终结构和普通对象Map类似,但因创建路径不同,内部标识(如
instanceof关联、构造函数引用)存在差异,所以haveSameMap返回false。 - 带字段声明的ES6类实例Map,虽最终属性列表与前两者一致,但关联了类的
prototype和字段声明元数据,属于ES6类专属Map类型,与前两者并非同一实例。
而debugPrint输出的是Map的结构信息(如属性名称、偏移量、可写性等),三者最终结构一致,所以看起来相似,但Map内部标识和关联信息不同,因此被视为不同Map。
内容的提问来源于stack exchange,提问作者Julien Barrois
相关产品推荐
相关产品推荐

