Vue3.0数据响应系统分析(主要针对于reactive)

Vue3.0与Vue2.x的数据响应机制

Vue3.0采用了ES6的Proxy来进行数据监听javascript

优势:java

1. 对对象进行直接监听, 能够弥补Object.defineProperty没法监听新增删除属性的短板

2. 无需在遍历对象进行设置监听函数

3. 能够适用于Array, 不须要再分红两种写法
复制代码

缺点:react

1. 兼容性不足, 致使目前IE11没法使用
复制代码

源码导读

在分析源码以前,咱们须要得知几个变量先:性能优化

rawToReactive:
    类型: <WeakMap>
    值: {
      原始对象: Proxy对象
    }

  reactiveToRaw:
    类型: <WeakMap>
    值: {
      Proxy对象: 原始对象
    }

  targetMap: 
    类型: <WeakMap>
    值: {
       原始对象: new Map([key, new Set([effect])]) // key是原始对象里的属性, 值为该key改变后会触发的一系列的函数, 好比渲染、computed
    }
复制代码

首先咱们来看一下reactive函数bash

export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

复制代码

首先咱们检测了原始对象是不是只读的代理对象, 紧接着又检测了是不是只读对象, 缘由是readonly对象是不容许进行修改编辑, 因此是不须要进行响应处理, 接下来, 主要的响应系统都在createReactiveObject里, 下面对该函数进行分析函数

function createReactiveObject( target: any, toProxy: WeakMap<any, any>, // { originObj: proxyObj } toRaw: WeakMap<any, any>, // { proxyObj: originObj } baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any> ) {
    if (!isObject(target)) {
      if (__DEV__) {
        console.warn(`value cannot be made reactive: ${String(target)}`)
      }
      return target
    }
    // target already has corresponding Proxy
    let observed = toProxy.get(target) 
    if (observed !== void 0) { 
      return observed
    }
    
    // target is already a Proxy
    if (toRaw.has(target)) {
      return target
    }

    // only a whitelist of value types can be observed.
    if (!canObserve(target)) {
      return target
    }

    const handlers = collectionTypes.has(target.constructor)
      ? collectionHandlers
      : baseHandlers
    observed = new Proxy(target, handlers)
    toProxy.set(target, observed)
    toRaw.set(observed, target)
    
    if (!targetMap.has(target)) {
      targetMap.set(target, new Map())
    }
    return observed
  }
复制代码

首先对于类型进行了判断,若不是对象形式则会报错,而且不进行响应式处理 其次对是否已经处理过该对象进行了判断,因为Proxy对象与原对象已经不是同一个指针,因此Vue对两个对象进行了分别的判断性能

canObserve判断是否符合如下四个条件优化

对象符合如下配置
   * 1. 不是Vue实例
   * 2. 不是虚拟DOM
   * 3. 是属于Object|Array|Map|Set|WeakMap|WeakSet其中一种
   * 4. 不存在于nonReactiveValues
复制代码

handlers这里判断了两种状况:ui

  1. 如果Map|Set|WeakMap|WeakSet的一种则采用collectionHandlers
  2. 不然采用baseHandlers

最后进行原对象的代理处理, 而且绑定了二者的关系, 在这里咱们看见了targtMap的绑定, 这个WeakMap对于数据响应起到了很关键的做用,咱们下面会讲到,先看下面, 紧接着就返回了代理对象spa

接下来咱们来看下代理对象handler的处理

因为百分之99的处理都是由baseHandlers来处理,那么咱们接下来就针对这个handlers进行分析

export const mutableHandlers: ProxyHandler<any> = {
    get: createGetter(false),
    set,
    deleteProperty,
    has,
    ownKeys
  }
复制代码

很简单的一个赋值, 咱们从get函数开始分析

function createGetter(isReadonly: boolean) {
    return function get(target: any, key: string | symbol, receiver: any) {
      const res = Reflect.get(target, key, receiver)
      if (typeof key === 'symbol' && builtInSymbols.has(key)) {
        return res
      }
      if (isRef(res)) {
        return res.value
      }
      
      track(target, OperationTypes.GET, key)
      return isObject(res)
        ? isReadonly
          ? // need to lazy access readonly and reactive here to avoid
            // circular dependency
            readonly(res)
          : reactive(res)
        : res
    }
  }
复制代码

首先获取了该属性的值,而后咱们看见Vue对Symbol的一些类型进行分辨, 如果符合条件则直接返回, 接下来这里划重点,要考, track函数对于数据响应起到了相当重要的做用, 咱们来看下track函数源码是怎么写的

export function track( target: any, type: OperationTypes, key?: string | symbol ) {
    if (!shouldTrack) {
      return
    }
    const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1]

    if (effect) {
      if (type === OperationTypes.ITERATE) {
        key = ITERATE_KEY
      }
      let depsMap = targetMap.get(target) 
      if (depsMap === void 0) {
        targetMap.set(target, (depsMap = new Map()))
      }
      
      let dep = depsMap.get(key!)
      if (dep === void 0) {
        depsMap.set(key!, (dep = new Set()))
      }
      if (!dep.has(effect)) {
        dep.add(effect)
        effect.deps.push(dep)
        if (__DEV__ && effect.onTrack) {
          effect.onTrack({
            effect,
            target,
            type,
            key
          })
        }
      }
    }
  }
复制代码

首先这里定义了一个shouldTrack, 这个变量是用来控制调用生命周期的时候的开关,防止触发屡次

获取targetMap里该对象各个属性的值, 若没有,则进行数据初始化new Set, 而且将effect添加到了该集合里, 这里咱们看见了 不止dep添加了, effect也添加了, 这里是有缘由的,咱们等下进行分析

看到这里, 相信你们都明白了track函数使用进行数据依赖采集的, 以便于后面数据更改可以触发对应的函数

接下来咱们分析下set函数

function set( target: any, key: string | symbol, value: any, receiver: any // proxy对象 ): boolean {
    value = toRaw(value)
    const hadKey = hasOwn(target, key)
    const oldValue = target[key]
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    }
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) { // 判断更改对象是不是
      /* istanbul ignore else */
      if (__DEV__) {
        const extraInfo = { oldValue, newValue: value }
        if (!hadKey) {
          trigger(target, OperationTypes.ADD, key, extraInfo)
        } else if (value !== oldValue) {
          trigger(target, OperationTypes.SET, key, extraInfo)
        }
      } else {
        if (!hadKey) {
          trigger(target, OperationTypes.ADD, key)
        } else if (value !== oldValue) {
          trigger(target, OperationTypes.SET, key)
        }
      }
    }
    return result
  }
复制代码

首先一开始也是对于各个类型进行分析而且处理对应的状况, 咱们主要看一下trigger函数

export function trigger( target: any, // 原始对象 type: OperationTypes, // 判断是替换仍是增长等等操做 key?: string | symbol, // 对象属性 extraInfo?: any ) {
    const depsMap = targetMap.get(target) // new Map()
    // console.log('depsMap1', depsMap);
    if (depsMap === void 0) {
      // never been tracked
      return
    }
    const effects = new Set<ReactiveEffect>()
    const computedRunners = new Set<ReactiveEffect>()
    if (type === OperationTypes.CLEAR) {
      // collection being cleared, trigger all effects for target
      depsMap.forEach(dep => {
        addRunners(effects, computedRunners, dep)
      })
    } else {
      // console.log(key);
      // schedule runs for SET | ADD | DELETE
      if (key !== void 0) {
        addRunners(effects, computedRunners, depsMap.get(key))
      }
      // also run for iteration key on ADD | DELETE
      if (type === OperationTypes.ADD || type === OperationTypes.DELETE) {
        const iterationKey = Array.isArray(target) ? 'length' : ITERATE_KEY
        addRunners(effects, computedRunners, depsMap.get(iterationKey))
      }
    }
    const run = (effect: ReactiveEffect) => {
      scheduleRun(effect, target, type, key, extraInfo)
    }
    // Important: computed effects must be run first so that computed getters
    // can be invalidated before any normal effects that depend on them are run.
    computedRunners.forEach(run)
    effects.forEach(run)
  }
复制代码

在trigger函数里咱们看到了两个集合变量, effects与computedRunners, 两个集合针对两种类型进行数据采集我么往下看

引入眼帘的估计就是addRunners, 咱们猜想下 这个addRunners顾名思义应该就是添加执行任务, 下面看下源码

function addRunners( effects: Set<ReactiveEffect>, computedRunners: Set<ReactiveEffect>, effectsToAdd: Set<ReactiveEffect> | undefined // Effects集合 ) {
    // console.log('effectsToAdd', effectsToAdd);
    if (effectsToAdd !== void 0) {
      effectsToAdd.forEach(effect => {
        if (effect.computed) {
          computedRunners.add(effect)
        } else {
          effects.add(effect)
        }
      })
    }
  }
复制代码

effectsToAdd就是track函数里添加的对象属性的值 new Set, 用于收集依赖的, 根据effect是不是计算属性来分别添加到不一样的集合下, 回到trigger函数里, 咱们看见了effects与computedRunners进行遍历执行, 那么咱们在分析下具体的scheduleRun函数

function scheduleRun( effect: ReactiveEffect, target: any, type: OperationTypes, key: string | symbol | undefined, extraInfo: any ) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(
        extend(
          {
            effect,
            target,
            key,
            type
          },
          extraInfo
        )
      )
    }
    
    if (effect.scheduler !== void 0) {
      effect.scheduler(effect)
    } else {
      effect()
    }
  }
复制代码

这里咱们看见了Vue对effect进行了两种状况的判断, 首先判断了effect.scheduler是否存在, 若存在则使用scheduler来调用effect, 不存在则进行直接调用, 那么scheduler究竟是什么呢? 这里的scheduler就是Vue的性能优化点,放入队里里, 等到miscroTask里进行调用, 熟悉Vue2.x的同窗都知道nextTick函数, 这个scheduler能够看作就是调用了nextTick函数

咱们来看下effect具体是什么

const effect: ReactiveEffect = function effect(...args: any[]): any {
    return run(effect as ReactiveEffect, fn, args)
  }

  function run(effect: ReactiveEffect, fn: Function, args: any[]): any {
    if (!effect.active) {
      return fn(...args)
    }

    if (activeReactiveEffectStack.indexOf(effect) === -1) {
      cleanup(effect)
      // 初始化mount的时候会执行effect函数, 当前effect是componentEffect, 也就是渲染函数, 此时因为去获取了变量数据,也就是触发了get函数,get函数会触发track函数, track函数就是用来收集effect, 
      try {
        activeReactiveEffectStack.push(effect)
        return fn(...args)
      } finally {
        activeReactiveEffectStack.pop()
      }
    }
  }
复制代码

effect实际上就是运行了run函数, 咱们看下run函数的运行, 在运行以前会先cleanup, 这里咱们就要返回以前所说的track函数, 你们还记得track函数里, 不仅dep添加了effect, effect也同时添加了dep吗? 缘由就在这里, cleanup须要用到

function cleanup(effect: ReactiveEffect) {
    const { deps } = effect
    // console.log('deps', deps);
    if (deps.length) {
      for (let i = 0; i < deps.length; i++) {
        deps[i].delete(effect)
      }
      deps.length = 0
    }
  }
复制代码

该函数清空了dep里全部的依赖, 那么胆大心细的同窗会发现一个问题:

在track函数里已经添加了effect, 那么为何在这里要从新清除掉全部的依赖呢?

理论上看起来是个很鸡肋的操做, 可是实际上Vue已经考虑了全方面, 试想一个场景: A组件与B组件是经过v-if来控制展现, 当A组件首先渲染以后, 所对应的的数据就会采集对应的依赖, 此时更改v-if条件, 渲染了B组件, 如果B组件此时更改了A组件里的变量, 如果A组件的依赖没有被清除掉, 那么会产生没必要要的依赖调用, 因此Vue要事先清除掉全部的依赖, 确保依赖始终是最新的

分析到这咱们已经清楚了Vue3.0的数据响应到底是如何了!

总结

Vue3.0从根本上解决了Vue2.x数据响应系统留下的短板, 可是兼容性上还存在问题,采用尤大一句话, IE11百足之虫,死而不僵, 暂时还不能彻底抛弃IE11, 但愿后期能有新的突破!

相关文章
相关标签/搜索