总是听说 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 更友好,更现代,操作能力更强。

相关推荐
We་ct15 分钟前
LeetCode 5. 最长回文子串:DP + 中心扩展
前端·javascript·算法·leetcode·typescript
陈随易4 小时前
有生之年系列,Nodejs进程管理pm2 v7.0发布
前端·后端·程序员
冰暮流星4 小时前
javascript之事件代理/事件委托
前端
@yanyu6665 小时前
登录注册功能-明文
vue.js·springboot
陈随易6 小时前
AI时代,你还在坚持手搓文章吗
前端·后端·程序员
里欧跑得慢8 小时前
17. Flutter Hero动画实现:让界面过渡更加优雅
前端·css·flutter·web
IT_陈寒8 小时前
Vue的这个响应式陷阱,我debug了一整天才爬出来
前端·人工智能·后端
kyriewen8 小时前
前端测试:别为了100%覆盖率而写测试,那是自欺欺人
前端·javascript·单元测试
去伪存真9 小时前
我自己写的第一个skills--project-core-standards
前端·agent
Data_Journal9 小时前
如何使用cURL更改User Agent
大数据·服务器·前端·javascript·数据库