Vue3 对Proxy 、defineProperty的选择
1. Proxy 的性能,可能比 defineProperty 更差
我们在大数据量循环的过程中,分别用 Object.defineProperty
与 Proxy
劫持的数据,执行一次 getter
与 setter
。然后利用 performance.now 记录执行时间。
先看 defineProperty
的案例。
首先定义一个简单对象
js
// 在循环中,我们会执行计算操作
var target = {
total: 0
}
然后另外定义一个普通变量用于存储劫持过程中访问和设置的值。
然后用 Object.defineProperty
劫持 target。
js
Object.defineProperty(target, 'count', {
get: function () {
return b;
},
set: function (value) {
b = value;
},
});
然后循环 1000000 次,并打印执行时间
js
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += target.count;
target.count = index;
}
console.log('defineProperty', performance.now() - now)
接下来看使用 Proxy
的案例。
也是首先定义一个普通对象
js
var target = {
count: 0
}
然后使用 Proxy
代理。
js
let proxy = new Proxy(target, {
get: (target, prop, receiver) => {
return Reflect.get(target, prop, receiver)
},
set(target, prop, value) {
return Reflect.set(target, prop, value)
}
});
然后循环访问 getter 和 setter
js
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
proxy.count
}
console.log('Proxy', performance.now() - now)
完整代码如下
js
<script>
var end = 1000000
var b = 0;
var target = {
count: 0
}
Object.defineProperty(target, 'count', {
get: function () {
return b;
},
set: function (value) {
b = value;
},
});
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += target.count;
target.count = index;
}
console.log('defineProperty', performance.now() - now)
</script>
js
<script>
var end = 1000000
var target = {
count: 0
}
let proxy = new Proxy(target, {
get: (target, prop, receiver) => {
return Reflect.get(target, prop, receiver)
},
set(target, prop, value) {
return Reflect.set(target, prop, value)
}
});
var total = 0;
var now = performance.now()
for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
}
console.log('Proxy', performance.now() - now)
</script>
在 chrome 中执行结果为:
执行时间上,Proxy 用时更久。
在 safari 中执行结果为:
在 safari 浏览器中,Proxy 的性能严重低于 defineProperty.
firefox 的执行结果差异最大。
然后尝试让 Proxy 代理的对象增加层级,然后进行 set 操作
注意,这里只是简单的增加对象复杂度,并不代表更深层级的属性也能被代理。
js
var target = {
count: 0,
b: {
c: 0
}
}
js
for (let index = 0; index < end; index++) {
total += proxy.count;
proxy.count = index;
proxy.b.c = target.count
}
验证结果发现,当层级变深,执行消耗的时间越长。下图是 chrome 的执行结果。
结论
在常用的几种浏览器中,Proxy 的性能都弱于 defineProperty,在 safari,firefox 中,defineProperty 的性能大幅度领先。针对 Proxy 的性能,chrome 优化做得最好。但依然小幅度弱于 defineProperty。
2. Proxy相对于defineProperty的优势
-
Proxy可以直接监听对象而非属性
-
Proxy可以直接监听数组的变化
- 当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length 的变化,我们可以借此进行操作,以上
Object.defineProperty
无法生效。
- 当我们对数组进行操作(push、shift、splice等)时,会触发对应的方法名称和length 的变化,我们可以借此进行操作,以上
-
Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是
Object.defineProperty
不具备的。 -
Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而
Object.defineProperty
只能遍历对象属性直接修改。 -
Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利。
3. Vue3 对Proxy 、defineProperty的选择
- 根据以上知识,可知Proxy 的功能更强大,但需要更多的开销;
- 所以,Vue3 里用于定义复杂数据类型的Reactive选择Proxy,用于定义简单数据类型的ref使用defineProperty劫持getter/setter