Vue2和Vue3的响应式

一、Vue2的响应式原理

Vue2.js主要是运用Object.defineProperty()方法直接在一个对象的具体属性上通过设置get和set,来拦截对象属性的get和set属性。

1.1 相关源码分析

const  dep = new Dep()   //依赖管理器
export function defineReactive(obj, key, val){
val = obj[key]   //计算出对应的key的值
observe(val) 
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){  //触发依赖收集
If(Dep.target){   //之前赋值的当前watcher实例
dep.addDep(Dep.target)  //收集起来 放入上面的dep依赖管理器内
。。。
}
},
set(newVal){  //派发更新
If(newVal === val){  //相同
return;
}
val = newVal // 赋值
observe(val); // 若新值变成响应式的
dep.notify()  //通知更新
})
}

分析:利用observe()方法将数据编程响应式数据,observe()为Observer类的工厂方法,最后一句为return new Observer(value)。执行new Observer(value)将传入的对象挂载到当前的this下,然后再利用walk()进行遍历,递归其对象的每一项,对每一项依次传入defineReactive()方法中执行,使数据都变为响应式数据。

收集依赖:利用get属性,将依赖项都存入Dep依赖管理器中。

派发更新:利用set属性,若数据重新赋值,需要用observe()方法将新的数据变为响应式,同时触发依赖管理器dep.notify(),即对dep中数组收集起来的watcher挨个触发update(),发出视图更新通知。

let uid = 0
class Dep{
  constructor() {
    this.id = uid++
    this.subs = []
  }
  
  notify() {  // 通知
    const subs = this.subs.slice()
    for(let i = 0, i < subs.length; i++) {
      subs[i].update()  // 挨个触发watcher的update方法
    }
  }
}

利用update()将watcher实例传入queueWatcher()方法内,即收集到一个队列中。

class Watcher{
  ...
  update() {
    queueWatcher(this)
  }
}

注意:若同一个watcher内触发了多次更新,只会更新一次对应的watcher。

function queueWatcher(watcher) {
  const id = watcher.id
  if(has[id] == null) {  // 如果某个watcher没有被推入队列
    ...
    has[id] = true  // 已经推入
    queue.push(watcher)  // 推入到队列
  }
  ...
  nextTick(flushSchedulerQueue)  // 下一个tick更新(是this.$nextTick方法的原始方法)
}

派发更新的粒度上组件级别的,如何高效更新试图是Diff算法及其之后的操作。

因为watcher有before属性,执行传入的before方法触发beforeUpdate()钩子,执行watcher.run(),执行执行getAndInvoke(),执行this.get(),再执行一次vm._update(vm._render()),vm._render()方法利用creatElement()生成虚拟节点,vm._update()里面执行patch()方法进行Diff算法,新旧虚拟节点进行对比,生成真实节点,更新视图。

2.Object.defineProperty()存在的问题

1. 只能拦截对象属性的get和set操作,无法拦截delete、in、方法调用等操作。

2. 一次只能对一个属性实现数据挟持,需要遍历所有属性进行挟持。

3. 无法响应式处理新增属性和删除属性。需使用 this.$set()设置新属性,使用 this.$delete() 删除属性。

4. 无法监听数组下标的变化。

5. 对于数组而言,大部分操作都拦截不到,需要对Array原型支持的方法进行对应改写。

6. 直接对原对象进行直接改写属性监听。

二、Vue3的响应式原理

利用Proxy实现数据挟持,创建一个对象的代理,实现基本操作的拦截和自定义。本质上是通过拦截对象的内部方法的执行实现代理。

Vue3中提供了reactive()和ref()两个方法将目标数据变为响应式数据。

利用Proxy实现数据劫持的优势:

1. 解决了使用Object.defineProperty()继续数据劫持的缺点,完美监听任何方式导致的数据改变行为。

2. 对于整个拦截对象直接进行挟持,无需遍历属性依次添加定义get和set属性。

3. 对于原对象生成拦截对象,然后对拦截对象进行相应监听行为,确保原对象不变。

2.1 相关源码分析

从源码看,核心是creatReactiveObject()函数

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  // 若目标对象是响应式的只读数据,则直接返回
  if (isReadonly(target)) {
    return target
  }
  // 否则将目标数据尝试变成响应式数据
  return createReactiveObject(
    target,
    false,
    mutableHandlers, // 对象类型的 handlers
    mutableCollectionHandlers, // 集合类型的 handlers
    reactiveMap
  )
}

creatReactiveObject()函数内为一些判断处理

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {

  // 非对象类型直接返回
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }

  // 目标数据的 __v_raw 属性若为 true,且是【非响应式数据】或 不是通过调用 readonly() 方法,则直接返回
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }

  // 目标对象已存在相应的 proxy 代理对象,则直接返回
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 只有在白名单中的值类型才可以被代理监测,否则直接返回
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target     
  }

  // 创建代理对象
  const proxy = new Proxy(
    target,
    // 若目标对象是集合类型(Set、Map)则使用集合类型对应的捕获器,否则使用基础捕获器
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers 
  )

  // 将对应的代理对象存储在 proxyMap 中
  proxyMap.set(target, proxy)

  return proxy
}

具体的实现又在不同数据类型的捕获器中,即collectionHandlers和baseHandles,其对于于reactive()函数中的createReactiveObject()函数传递的mutableCollectionHandlers和mutableHandlers。

学习参考:听说你很了解 Vue3 响应式?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值