Vue2 和 Vue3 响应式原理

229 阅读2分钟

前置知识

Object.defineProperty 参考链接

Proxy 参考链接

Reflect 参考链接

vue2 的响应式

通过 Object.defineProperty 对对象的已有属性值的读取和修改方法进行重写, 此外Object.defineProperty 不能对数组的属性的读写进行拦截, 数组需要通过重写数组的一些方法来实现对元素操作的。

用 Object.defineProperty 实现对对象中所有属性的读写访问进行拦截

// 定义一个对象
let user = {
  name: "mike",
  age: 12
}
// 遍历所有对象中的 key 进行属性的拦截,因为需要遍历所有属性,所以会有性能上的影响。
for (let key in user) {
  let value = user[key]
  Object.defineProperty(user, key, {
    get() {
      console.log( "value = " + value);
      return value;
    },
    set(newValue){
      if (value !== newValue) {
        // 监听新赋值的
        // TODO
        this.observer(newValue);
        console.log( value + "(old)"+ " ==> "+ newValue + "(new)");
        value = newValue;
      }
    }
  })
}
user.name = { name: 'abc' };

通过重写数组 Array 的方法,实现对数组读取的拦截

例如重写数组中的 push,pop,shift,unshift 方法:

// 拷贝一份 Array 的原型方法
let proto = Object.create(Array.prototype);
// 重写数组中的 push,pop,shift,unshift 方法
['push','pop','shift','unshift'].forEach(methodName=>{
  // 重写 methodName 指向的方法,最终还是需要调用 Array 原型方法,
  // 相当于拦截该方法的执行,在拦截中实现自己的操作
  proto[methodName]=function(){
    // TODO
    console.log("执行数组中"+ methodName +"方法")
    Array.prototype[methodName].call(this,...arguments)
  }
})
let arr = [1,2,3,4,5,6,7,8]
arr.__proto__ = proto
arr.push(9) // 执行数组中push方法 1,2,3,4,5,6,7,8,9] 

vue3 的响应式

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

用 Proxy 代理对象

let user = {
  name: "he.wenyao",
  age: 18
}
// 创建一个代理对象 user_proxy 是对 user 的一个代理,
// 对 user_proxy 所作的操作也会同步到 user 上
let user_proxy = new Proxy(user, {
  set: function (target, key, value, receiver) {
    // TODO 你想在赋值操作的时候做点什么?
    console.log(`赋值操作${String(key)} = ${value}`);
    return Reflect.set(target, key, value, receiver);
  },
  get: function (target, key, receiver) {
    // TODO 你想在获取值的时候做点什么?
    return Reflect.get(target, key, receiver);
  }
});

user_proxy['age'] = 1; //  赋值操作age = 1 \n 1

用 Proxy 代理数组

let arr = [1, 2, 3]
let proxy = new Proxy(arr, {
  get: function (target, key, receiver) {
    console.log('get的key为 ===>' + key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log('set的key为 ===>' + key, value);
    return Reflect.set(target, key, value, receiver);
  }
})
proxy[0]          // set的key为 ===>0
proxy[3] = 12     // set的key为 ===>3  12

Vue2 和 Vue3 代理的区别

  1. Vue2 中使用 Object.defineProperty 在重写对象的读写访问时,只能一个个属性进行设置,需要遍历对象中的所有属性,才能完成对一个对象的所有属性的读写拦截,影响性能,而且不能对数组的属性读写进行拦截,数组属性读写的拦截只能通过复写一个个数组属性方法。
  2. Vue2 中使用 Proxy 对对象和数组的所有属性的读写进行拦截,不用一个个属性进行拦截。