vue响应式原理源码分析

vue是怎么实现响应式更新的

  • vue2: 在组件实例化时,递归遍历对象的每个属性,使用 Object.defineProperty() 重新定义 getter 和 setter ,每次访问属性时触发 getter,收集依赖(Dep),修改属性值时触发 setter,通知依赖更新(watcher),最后实现视图的更新,但是defineProperty也是有缺点的.
    主要有三个缺点:
  1. 对于复杂的对象需要深度监听,必须递归遍历整个对象,性能开销大,它的计算量是非常大的,性能也不是很好
  2. 对于对象的新增和删除属性是无法监听的,所以它需要使用Vue.$set()和Vue.$delete()来作为辅助操作
  3. 对于数组,由于不能直接拦截数组的索引和长度变化,Vue 重写了部分会改变数组内容的方法 [‘push’,’pop’,’shift’,’unshift’,’splice’’sort’,’reverse’],比较繁琐
  • vue3: 对于vue3的响应式原理是利用Proxy和reflect来做的,替代了 Vue 2 中对 Object.defineProperty 的使用。我们通过new Proxy来创建代理对象,第一个参数放要被代理的原始对象,第二个参数是一个拦截器对象handler,里面放的是函数,每个函数对应后期用户操作代理的一个捕获器,用户只要有对应的操作就会进入对应的捕获函数去执行,比如 get、set、deleteProperty 等,用来拦截对目标对象的读取、修改、删除等操作。
    当访问代理对象的属性时,会触发get捕获器,内部调用 track()函数收集依赖,收集effect函数,reflect反射返回原对象的值
    当修改代理对象的属性时,会触发set捕获器,会执行 trigger(), 找到之前依赖该属性的所有 effect, 逐个执行这些 effect 函数,从而更新视图
    优势
  1. 它直接代理整个对象而不需要遍历监听属性,性能会有所提升。
  2. proxy它可以直接监听数组的变化,而不需要去重写数组的原生方法。
  3. proxy有多达13种的拦截方法,它的功能会更加强大

Proxy和reflect反射执行对象操作(原始行为)

const user = {
  name: 'Alice',
  age: 25
};
const proxy = new Proxy(user, {
  // 放的是函数,每个函数对应后期用户操作代理的一个捕获器
  // 用户只要有对应的操作就会进入对应的捕获函数去执行
  get(target, key, receiver) {
    console.log(`读取属性:${key}`);
    // 还要收集依赖 vue3的依赖是函数effect
    return Reflect.get(target, key, receiver); // 保持原始行为
  },
  set(target, key, value, receiver) {
    console.log(`设置属性:${key} = ${value}`);
    // 触发依赖更新 直接找到存储的effect函数依赖调用更新页面
    return Reflect.set(target, key, value, receiver); // 设置(增改)并返回布尔值
  },
  deleteProperty(target, key) {
    console.log(`删除属性:${key}`);
    return Reflect.deleteProperty(target, key);
  }
});

// 使用代理对象
console.log(proxy.name);         // 触发 get
proxy.age = 26;                  // 触发 set
delete proxy.name;              // 触发 delete