总是听说 Vue3 选择 Proxy 的原因是性能更好,不如直接上代码对比对比
逛掘金的时候经常能刷到关于 Vue
响应式原理的文章, 经常能看到 Vue3
弃用 Object.defineProperty
转而使用 Proxy
来实现的原因是 Proxy 性能更好
。看的多了还能刷到一些文章认为 Object.defineProperty
性能更好,因此自己创建了一个小 demo 来对比二者在不同场景下的性能。
以下测试仅在
谷歌浏览器
中进行,不同浏览器内核不同,结果可能有差异。可以访问此 在线地址 测试其他环境下的性能。
封装响应式
本文不会详细解析基于 Object.defineProperty
和 Proxy
的封装代码,这些内容在多数文章中已有介绍。Vue3
对嵌套对象的响应式处理进行了优化,采用了一种惰性添加
的方式,仅在对象被访问时才添加响应式。相比之下,Vue2
采用了一次性递归处理整个对象的方式添加响应式。为了确保比较的公平性,本文下面的 Object.defineProperty
代码也采用了相同的惰性添加策略。
Object.defineProperty
/** Object.defineProperty 深度监听 */
export function deepDefObserve(obj, week) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
let value = obj[key]
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
if (
typeof value === "object" &&
value !== null &&
week &&
!week.has(value)
) {
week.set(value, true)
deepDefObserve(value)
}
return value
},
set(newValue) {
value = newValue
},
})
}
return obj
}
Proxy
/** Proxy 深度监听 */
export function deepProxy(obj, proxyWeek) {
const myProxy = new Proxy(obj, {
get(target, property) {
let res = Reflect.get(target, property)
if (
typeof res === "object" &&
res !== null &&
proxyWeek &&
!proxyWeek.has(res)
) {
proxyWeek.set(res, true)
return deepProxy(res)
}
return res
},
set(target, property, value) {
return Reflect.set(target, property, value)
},
})
return myProxy
}
测试性能
测试场景有五个:
- 使用两个
API
创建响应式对象的耗时,即const obj = reactive({})
的耗时 - 测量对已创建的响应式对象的属性进行访问的速度,即
obj.a
的读取时间。 - 测量修改响应式对象属性值的耗时,即执行
obj.a = 1
所需的时间。 - 创建多个响应式对象,并模拟访问和修改它们属性的操作,以评估在多对象场景下的性能表现。
- 针对嵌套对象进行响应式性能测试,以评估在复杂数据结构下的性能表现。
初始化性能
const _0_calling = {
useObjectDefineProperty() {
const data = { a: 1, b: 1, c: 1, d: 1, e: 1 }
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
Object.defineProperty(data, keys[i], {
get() {},
set() {},
})
}
},
useProxy() {
const data = { a: 1, b: 1, c: 1, d: 1, e: 1 }
const proxy = new Proxy(data, {
get() {},
set() {},
})
},
}
很明显,Proxy
的性能优于 Object.defineProperty
。
读取性能
const readDefData = deepDefObserve({ a: 1, b: 1, c: 1, d: 1, e: 1 })
const readProxyData = deepProxy({ a: 1, b: 1, c: 1, d: 1, e: 1 })
export const _1_read = {
useObjectDefineProperty() {
readDefData.a
readDefData.b
readDefData.e
},
useProxy() {
readProxyData.a
readProxyData.b
readProxyData.e
},
}
Object.defineProperty
明显优于 Proxy
。
写入性能
const writeDefData = deepDefObserve({ a: 1, b: 1, c: 1, d: 1, e: 1 })
const writeProxyData = deepProxy({ a: 1, b: 1, c: 1, d: 1, e: 1 })
export const _2_write = {
count: 2,
useObjectDefineProperty() {
writeDefData.a = _2_write.count++
writeDefData.b = _2_write.count++
},
useProxy() {
writeProxyData.a = _2_write.count++
writeProxyData.b = _2_write.count++
},
}
Object.defineProperty
优于 Proxy
,不过差距不大。
多次创建及读写
export const _4_create_read_write = {
count: 2,
useObjectDefineProperty() {
const data = { a: 1, b: 1, c: 1, d: 1, e: 1 }
deepDefObserve(data)
data.a = _4_create_read_write.count++
data.b = _4_create_read_write.count++
data.a
data.c
},
proxyWeek: new WeakMap(),
useProxy() {
const data = { a: 1, b: 1, c: 1, d: 1, e: 1 }
const proxy = deepProxy(data, _4_create_read_write.proxyWeek)
proxy.a = _4_create_read_write.count++
proxy.b = _4_create_read_write.count++
proxy.a
proxy.c
},
}
Proxy
优势更大,但这个场景并不多见,很少会出现一次性创建大量响应式对象的情况,对属性的读写场景更多。
对嵌套对象的性能
对内部的每个属性都进行读或写操作
const deepProxyWeek = new WeakMap()
const defWeek = new WeakMap()
export const _5_deep_read_write = {
count: 2,
defData: deepDefObserve(
{
res: {
code: 200,
message: {
error: null,
},
data: [
{
id: 1,
name: "1",
},
{
id: 2,
name: "2",
},
],
},
},
defWeek
),
useObjectDefineProperty() {
_5_deep_read_write.defData.res.code = _5_deep_read_write.count++
_5_deep_read_write.defData.res.data[0].id = _5_deep_read_write.count++
_5_deep_read_write.defData.res.message.error
_5_deep_read_write.defData.res.data[0].id
_5_deep_read_write.defData.res.data[0].name
_5_deep_read_write.defData.res.data[1].id
_5_deep_read_write.defData.res.data[1].name
},
proxyData: deepProxy(
{
res: {
code: 200,
message: {
error: null,
},
data: [
{
id: 1,
name: "1",
},
{
id: 2,
name: "2",
},
],
},
},
deepProxyWeek
),
useProxy() {
_5_deep_read_write.proxyData.res.code = _5_deep_read_write.count++
_5_deep_read_write.proxyData.res.data[0].id = _5_deep_read_write.count++
_5_deep_read_write.proxyData.res.message.error
_5_deep_read_write.proxyData.res.data[0].id
_5_deep_read_write.proxyData.res.data[0].name
_5_deep_read_write.proxyData.res.data[1].id
_5_deep_read_write.proxyData.res.data[1].name
},
}
Object.defineProperty
会稍好一些,但两者的差距不大。
只读取修改嵌套对象的浅层属性
const _6_deepProxyWeek = new WeakMap()
const _6_defWeek = new WeakMap()
export const _6_update_top_level = {
count: 2,
defData: deepDefObserve(
{
res: {
code: 200,
message: {
error: null,
},
data: [
{
id: 1,
name: "1",
},
{
id: 2,
name: "2",
},
],
},
},
_6_deepProxyWeek
),
useObjectDefineProperty() {
_6_update_top_level.defData.res.code = _6_update_top_level.count++
_6_update_top_level.defData.res.message.error
},
proxyData: deepProxy(
{
res: {
code: 200,
message: {
error: null,
},
data: [
{
id: 1,
name: "1",
},
{
id: 2,
name: "2",
},
],
},
},
_6_defWeek
),
useProxy() {
_6_update_top_level.proxyData.res.code = _6_update_top_level.count++
_6_update_top_level.proxyData.res.message.error
},
}
这个场景 Proxy
略优于 Object.defineProperty
。
总结
Proxy
在对象创建时的性能明显优于Object.defineProperty
。而在浅层对象的读写性能方面,Object.defineProperty
表现更好。但是当对象的嵌套深度增加时,Object.defineProperty
的优势会逐渐减弱。尽管在性能测试中,Object.defineProperty
的读写优势可能更适合实际开发场景,但在 谷歌浏览器
中,Proxy
的性能与 Object.defineProperty
并没有拉开太大差距。因此,Vue3
选择 Proxy
不仅仅基于性能考量,还因为 Proxy
提供了更为友好、现代且强大的 API
,使得操作更加灵活。
来源:juejin.cn/post/7324141201802821672