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

相关推荐
自动花钱机3 分钟前
ESLint语法报错
前端·javascript·vue.js·css3·html5
予安灵23 分钟前
XSS 攻击(详细)
前端·web安全·网络安全·网络攻击模型·xss·安全架构·xss攻击
工业互联网专业26 分钟前
基于springcloud微服务架构的巡游出租管理平台
java·vue.js·spring cloud·微服务·毕业设计·源码·课程设计
好_快36 分钟前
Lodash源码阅读-cloneArrayBuffer
前端·javascript·源码阅读
╰つ゛木槿37 分钟前
Vue与React区别分析
前端·vue.js·react.js
thinkQuadratic37 分钟前
vue修饰符
前端·javascript·vue.js
不吃香菜的猪37 分钟前
el-dialog实现遮罩层后面的内容也能点击
前端·javascript·vue.js
好_快40 分钟前
Lodash源码阅读-cloneTypedArray
前端·javascript·源码阅读
好_快40 分钟前
Lodash源码阅读-cloneDataView
前端·javascript·源码阅读
好_快42 分钟前
Lodash源码阅读-copySymbols
前端·javascript·源码阅读