Skip to content

Vue3响应式原理

响应式

js
// Vue3.0 响应式原理

const toProxy = new WeakMap() // 放置的是 原对象:代理过的对象  防止多次代理同一个对象
const toRaw = new WeakMap() // 放置的是 代理过的对象:原对象 防止代理已经代理过的对象

// 判断是否为对象
function isObject(val) {
  return typeof val === 'object' && val !== null
}

// 判断key是否在这个对象上面
function hasOwn(target, key) {
  return Reflect.has(target, key)
}

// 响应式的核心方法
function reactive(target) {
  // 创建响应式对象
  return createReactiveObject(target)
}

// 创建响应式对象的方法 代理对象
function createReactiveObject(target) {
  if (!isObject(target)) {
    // 如果不是对象,直接返回
    return target
  }

  const alreadyProxy = toProxy.get(target) // 如果已经代理过了 就将代理过的对象返回
  if (alreadyProxy) {
    return alreadyProxy
  }

  if (toRaw.has(target)) {
    // 防止被代理过的对象被多次代理
    return target
  }

  const baseHandler = {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)

      // 收集依赖 订阅 把当前的 key 和 effect 对应起来
      track(target, key) // 如果目标上的 key 变化了 重新让数组中的 effect 执行即可

      return isObject(result) ? reactive(result) : result // 如果是对象的话就再代理一次,否则返回当前数据
    },

    set(target, key, value, receiver) {
      // 修改数组的时候,先修改值,然后length+1,会出发两次set
      const hadKey = hasOwn(target, key) // 判断对象时候有这个属性
      const oldValue = target[key]
      const res = Reflect.set(target, key, value, receiver)
      if (!hadKey) {
        // 如果对象上没有这个属性,就证明是新增一个属性
        console.log('新增属性')
      } else if (oldValue !== res) {
        // 如果老值和新值不相等,证明修改是有意义的,为了屏蔽无意义的修改
        console.log('修改属性')
      }

      const result = Reflect.set(target, key, value, receiver)
      return result
    },

    deleteProperty(target, key) {
      let result = Reflect.deleteProperty(target, key)
      return result
    }
  }

  const observed = new Proxy(target, baseHandler)

  toProxy.set(target, observed)
  toRaw.set(observed, target)
  return observed
}

Vue3 需要注意的点:

  1. 我们都知道 Vue2.0 代理对象会默认递归,所以在数据层级特别深的情况下,响应的速度会慢

    Vue3.0 的代理是在 get() 里面对当前属性进行代理,用就代理,不用就不代理,而且还会通过 toProxy 这个 weakMap 将代理过的对象存储起来,防止对象被多次代理,这样我们就判断是否代理过当前对象,如果没有代理过,那就对新对象代理

    反之,如果代理过了这个对象,那我们就直接返回代理过的对象,就行了

    还有一种情况是,代理过的对象又走了 reactive() 方法,这样就会导致对代理过的对象返回的 Proxy 再进行代理,显然我们是不希望看见这样的现象的,所以我们将已经代理过的对象存到 toRaw 这个 weakMap 里面,然后我们判断是否代理过这个对象,如果代理过,同样就返回代理过的对象就行了,这里一定要注意和 toProxy 区分开来。显然我们能发现 Vue3.0 的这种实现在性能上远胜于 Vue2.0

  2. Vue3.0 是能对数组进行代理的,它能够监听到数组的变化。但是我们知道当一个数组发生变化时,实际上过程是这样的:如果我们给数组 push 一个数据,它会先在数据的末尾添加一个数据,然后改变数组的 length 加 1,这样出现的问题就是,会出发两次 set() 方法,所以我们需要将新值和老值进行比较,如果不相同的话,那么就证明这个值是有意义的,它不是 length 这种属性,我们就执行赋值操作就可以了

  3. 由于 ES5 的限制,ES6 新增的 Proxy 无法被转译成 ES5,所以 Proxy 的兼容性就有了问题,也就导致 Vue3.0 无法兼容 IE

常备不懈,才能有备无患