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

相关推荐
come1123410 分钟前
Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
前端·javascript·vue.js
musk12121 小时前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
万少2 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL2 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
rzl022 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang2 小时前
前端如何实现电子签名
前端·javascript·html5
海天胜景2 小时前
vue3 el-table 行筛选 设置为单选
javascript·vue.js·elementui
今天又在摸鱼2 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js
蓝婷儿2 小时前
每天一个前端小知识 Day 21 - 浏览器兼容性与 Polyfill 策略
前端
百锦再2 小时前
Vue中对象赋值问题:对象引用被保留,仅部分属性被覆盖
前端·javascript·vue.js·vue·web·reactive·ref