为什么要用 Proxy API 代替 defineProperty API

1. Object.defineProperty

定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

为什么能实现响应式

通过 defineProperty 的两个属性:get 及 set。

缺点

  • 对一个对象进行 删除和添加属性 操作,无法劫持到。
  • 无法对 数组 API 进行监听。
  • 对对象深层的嵌套对象进行监听存在性能问题。

正因为 defineProperty 自身的缺陷,导致 Vue 2 在实现响应式过程中需要实现其他的方法辅助(如重写数组方法、增加额外 set、delete 方法)。

2. Proxy

Proxy 的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了:

function reactive(obj) {
  if (typeof obj !== 'object' && obj == null) {
    return obj
  }
  // Proxy 相当于在对象外层加拦截
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log(`获取 ${key}:${res}`)
      return res
    },
    set(target, key, value, receiver) {
      const res = Reflect.set(target, key, value, receiver)
      console.log(`设置 ${key}:${value}`)
      return res
    },
    deleteProperty(target, key) {
      const res = Reflect.deleteProperty(target, key)
      console.log(`删除 ${key}:${res}`)
      return res
    }
  })
  return observed
}

再测试嵌套对象情况,这时发现就不那么 OK 了:

const state = reactive({
  bar: { a: 1 }
})

// 设置嵌套对象属性
state.bar.a = 10 // no ok

如果要解决,需要在 get 之上再进行一层代理:

function reactive(obj) {
  if (typeof obj !== 'object' && obj == null) {
    return obj
  }
  // Proxy 相当于在对象外层加拦截
  const observed = new Proxy(obj, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver)
      console.log(`获取 ${key}:${res}`)
      return isObject(res) ? reactive(res) : res
    }
  })
  return observed
}

Proxy 的优势

  • Proxy 可以直接监听数组的变化(push、shift、splice 等)。
  • Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等,这是 Object.defineProperty 不具备的。
  • Proxy 天然支持 新增 / 删除属性 监听,不需要额外 set / delete 钩子。
  • Proxy 直接代理整个对象,不需要深度遍历 初始化的所有属性。

来源整理自:vue3js.cn 面试官系列