响应式原理之Proxy

77 阅读2分钟

1. Object.defineProperty

vue2 的响应式原理是基于 Object.defineProperty 实现的,虽然这种方式也可以实现,但是只能实现最基本的监听效果,因此 vue2 增加了 set,set, delete 等 api。对于新增属性、删除属性以及使用 in 操作来判断此对象是否含有某一个属性,这些操作都是无法直接监听到的。

1.1 vue2 响应式原理的核心步骤

Object.keys[obj].map(key => {
  let value = obj[key]
  Object.defineProperty(obj, key, {
    set: function(newValue) {
      console.log(`监听到给${key}设置值`)
      value = newValue
    },
    get: function() {
      console.log(`监听到获取${key}的值`)
      return value
    }
  })
})

2. Proxy

由于之前响应式的缺点,因此 vue3 使用的是 Proxy,大致思路就是使用 Proxy 代理将原来的 obj 对象给它代理到新的对象 objProxy,将所有对 obj 的操作都转移到 objProxy,因此当发生变化时就会执行相应的监听操作。

const obj = {
  name: 'fog',
  age: 18
}

// 1. 创建一个 Proxy 对象
const objProxy = {
  // 四个参数:target(目标对象),property(将被设置的属性key),value(新属性值),receiver(调用的代理对象)
  set: function(target, key, newValue) {
    console.log(`监听:监听${key}的设置值:`, newValue)
    target[key] = newValue
  },
  // 三个参数:target、property、receiver
  get: function(target, key) {
    console.log(`监听:监听${key}的获取`)
    return target[key]
  }
 
// 2. 对 obj 的所有操作,应该去操作 objProxy
console.log(objProxy.name)
objProxy.name = '123'
console.log(objProxy.name)

// objProxy 也会自动监听到
objProxy.address = 'bj'

3. Reflect

const obj = {
  _name: 'fog',
  get: function() {
    return this._name
  },
  set: function(newValue) {
    console.log("this: ", obj) // 默认是 obj
    this._name = newValue
  }
}

const objProxy = new Proxy(obj, {
  set: function(target, key, newValue, receiver) {
    const isSuccess = Reflect.set(target, key, newValue, recevier)
    
    if (!isSuccess) {
      throw new Error(`set ${key} failure`)
    }
  },
  get: function(target, key, receiver) {
    return Reflect.get(target, key, receiver)
  }
})

由于 Proxy 对象只能监听到 obj 对象这一层,当外层修改时 objProxy 对象能够监听到改变,但是深层的 _name 修改后并不能监听到,所以如果想要监听到深层次的变化,需要使用到 Reflect。 Reflect 的好处:

  1. 处理对象的目的:不再直接操作原对象
  2. Reflect.set 方法有返回值,可以判断本次操作是否成功
  3. receiver 就是外层 Proxy 对象,Reflect.set/get 最后一个参数,可以决定对象访问器 setter/getter 的 this 指向