用工厂函数替代ES6类后Vue应用性能骤降,求原因及优化方案
为什么工厂函数里用
Object.defineProperty比对象字面量定义getter/方法性能更好? 先理清楚你的场景:你把ES6类重构为不依赖this的工厂函数,一开始用对象字面量直接写getter和方法时,在Vue里处理几千个对象性能暴跌,换成Object.defineProperty后性能就回到了和类差不多的水平——核心原因就是后者定义的属性默认不可枚举,这恰好踩中了Vue响应式系统的性能关键点。
1. 可枚举属性拖慢了Vue的响应式处理
当你用对象字面量定义get myName()和say()时,这些属性默认是可枚举的。Vue在把对象变成响应式的时候,会遍历对象所有的可枚举属性(比如用for...in或者Object.keys()),给每个属性做依赖收集、添加响应式包装。
- 几千个对象的话,每个对象的可枚举getter和方法都会被Vue重复处理一遍,这就产生了大量不必要的遍历和包装开销;
- 而
Object.defineProperty默认的enumerable: false会让Vue的响应式系统直接跳过这些属性,不用做额外处理,性能自然就上来了。
2. ES6类为啥性能好?和Object.defineProperty是一个道理
其实ES6类的原型方法、getter,底层也是用类似Object.defineProperty的方式定义在原型上的,而且默认就是不可枚举的。这也是类的性能一开始就不错的原因:Vue不会去处理原型上的不可枚举方法,所有实例共享这些方法,不用每个实例都重复处理一遍。
给你一个更优的工厂函数方案
既然核心是让方法和getter避开Vue的响应式重复处理,我们可以把共享的方法、getter提取到一个原型对象里,让工厂函数返回的对象继承这个原型——这样既保留工厂函数的灵活性,又能像类一样共享方法,性能拉满:
// 定义共享的原型,方法和getter都放在这里,默认不可枚举 const PersonProto = { get myName() { return `My name is ${this.name}`; // 这里用this完全安全,因为实例继承了原型,this指向当前实例 }, say() { console.log(this.myName); } }; // 工厂函数 function Person(name) { // 用Object.create继承原型,同时初始化实例独有的属性 const obj = Object.create(PersonProto); obj.name = name; return obj; } const john = Person('John'); john.say(); // 输出 "My name is John"
这个方案的好处:
- 所有实例共享
PersonProto上的方法和getter,不用每个实例都重新定义,内存占用更低; - 原型上的属性默认不可枚举,Vue不会对它们做响应式处理,性能和ES6类完全一致;
- 依然是工厂函数的风格,不需要用
new关键字,兼顾了你想要的简洁性。
如果你的name属性需要在Vue中触发响应式更新,那它保持可枚举是必要的,这部分开销是合理的;而共享的方法和getter完全不需要响应式处理,所以放到原型上是最优解。
内容的提问来源于stack exchange,提问作者Eugene Barsky
相关产品推荐
相关产品推荐

