总是听说 Vue3 选择 Proxy 的原因是性能更好,不如直接上代码对比对比

逛掘金的时候经常能刷到关于 Vue 的文章, Vue3 弃用了 Object.defineProperty 转而使用 Proxy 来实现,很多文章在介绍的时候只有一句 Proxy 性能更好 就一笔带过,但还有一些文章认为 Object.defineProperty 性能更好,不过对比有点简单,因此自己创建了一个小 demo 来对比二者在不同场景下的性能。

以下测试仅在 谷歌浏览器 中进行,不同浏览器内核不同,结果可能有差异。可以访问此 在线地址 测试其他环境下的性能。

封装响应式

详细的代码解析就不写了,就是基于 Object.definePropertyProxy 的封装,大多数文章都有介绍,Vue3 中对嵌套对象的做了优化处理,可以惰性添加响应式,只有访问到的时候才会添加响应式。 Vue2 中使用了递归处理,一次性为整个对象添加响应式。二者对比起来不公平,因此下面的代码 Object.defineProperty 也使用惰性添加响应式。

Object.defineProperty

js 复制代码
/** 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

js 复制代码
/** 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
}

测试性能

测试场景有五个:

  1. 使用两个 API 创建响应式对象的耗时,即 const obj = reactive({}) 的耗时
  2. 创建一个响应式对象,测试对属性的访问速度,即 obj.a
  3. 修改值的耗时,即 obj.a = 1
  4. 创建多个响应式对象,并访问和修改其属性
  5. 嵌套对象的响应式性能

创建性能

js 复制代码
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

读取性能

js 复制代码
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

写入性能

js 复制代码
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,不过差距不大。

多次创建及读写

js 复制代码
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 优势更大,但这个场景并不多见,很少会出现一次性创建大量响应式对象的情况,对属性的读写场景更多。

对嵌套对象的性能

内部的每个属性都有读或写操作

js 复制代码
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
 },
}

两者的差距不大。

只读取修改嵌套对象的浅层属性

js 复制代码
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

总结

Object.defineProperty 在浅层对象的读写性能上更好,但是当对象嵌套的深度变大时,二者的差距就会缩小,而Proxy 的创建性能要明显优于 Object.defineProperty。如果只针对性能,在实际开发场景中,也许 Object.defineProperty 会更好,但是 Proxy 的性能在 谷歌浏览器 下也没有拉开太明显的差距。Vue3 选择 Proxy 应该综合的考量,API 更友好,更现代,操作能力更强。

相关推荐
前端爆冲4 分钟前
项目中无用export的检测方案
前端
小旋风0123410 分钟前
封装可拖动弹窗(vue jquery引入到html的版本)
vue.js·html·jquery
热爱编程的小曾32 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin43 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
麦麦大数据1 小时前
neo4j+django+deepseek知识图谱学习系统对接前后端分离前端vue
vue.js·django·知识图谱·neo4j·deepseek·在线学习系统
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员