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

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

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

封装响应式

详细的代码解析就不写了,就是基于 Object.defineProperty 和 Proxy 的封装,大多数文章都有介绍,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 更友好,更现代,操作能力更强。

相关推荐
小poop4 小时前
CSS 基础与实战:从入门到精通网页美化
前端
Csvn6 小时前
运行时性能
前端
Never_every997 小时前
位图转矢量工具哪个好?实测抠抠图AI位图转矢量工具
大数据·前端
paopaokaka_luck7 小时前
宠物门店经营管理系统(销售与库存联动、进货自动记账、 退换与退款关联、员工个人业绩、ECharts 经营分析、滞销识别与客户分层、宠物档案年龄计算)
前端·javascript·spring boot·mysql·echarts·mybatis·1024程序员节
空中湖8 小时前
用 React 写数据大屏:为什么我不建议用图表库
前端·react.js·前端框架
行者全栈架构师9 小时前
【鸿蒙心迹】让老照片变清晰:HarmonyOS 7.0 图像超分相册工程化落地与 5 个踩坑解法
架构·前端框架·开源
欣欣之王来了9 小时前
原始HTML插值:v-html指令的使用与安全风险
前端·学习·架构·项目·vue3教程
IT_陈寒9 小时前
Redis的LRU淘汰策略坑了我一整天
前端·人工智能·后端
计算机魔术师9 小时前
砍半价格、参数再涨,国产旗舰追到 Opus 鼻尖了
前端
paopaokaka_luck9 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节