本文基于Vue 3.2.30版本源码进行剖析
为了减少可读性,会对源码进行删减、调整程序、扭转的操作,文中所有源码均可视作为伪代码
因为ts版本代码携带参数过多,大部分伪代码会采取js的模式展现,而不是原来的ts代码
本文重点关注依赖收集和派发更新的流程,为了简化流程,会以Object为根底剖析响应式的流程,不会拘泥于数据类型不同导致的依赖收集和派发更新逻辑不同

本文内容

  • ProxyReflect的介绍
  • 整体依赖收集的流程图以及针对流程图的相干源码剖析
  • 整体派发更新的流程图以及针对流程图的相干源码剖析
  • ReactiveEffect外围类的相干源码剖析和流程图展现
  • computed的相干剖析,分为流程图和针对流程图的相干源码剖析
  • watchwatchEffect的初始化源码剖析,包含各种配置参数以及schedule
  • watchwatchEffect依赖收集和派发更新流程图展现
每一个点都有配套的流程图,源码剖析是为流程图服务,联合流程图看源码剖析成果更佳

前置常识

在Vue2源码-响应式原理浅析的文章剖析中,咱们晓得Vue2应用的是Object.defineProperty的形式,而在Vue3中应用Proxy进行代替数据的响应式劫持,上面将简略介绍Vue3中所应用的ProxyReflect

Proxy介绍

摘录于Proxy - JavaScript | MDN

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

Proxy只能代理对象,不能代理非对象值,比方String、Number、String、undefined、null等原始值

根底语法

// handler还有其它办法,上面示例只摘取Vue3中比拟罕用的5个办法const handler = {    get(target: Target, key: string | symbol, receiver: object) { },    set(target: object, key: string | symbol, value: unknown, receiver: object) { },    deleteProperty() { },    has() { },    ownKeys() { }}const p = new Proxy(target, handler);

参数

target

要应用 Proxy 包装的指标对象(能够是任何类型的对象,包含原生数组,函数,甚至另一个代理)

handler

一个通常以函数作为属性的对象,各属性中的函数别离定义了在执行各种操作时代理 p 的行为

Reflect的介绍

摘录于Reflect - JavaScript | MDN

Reflect 是一个内置的对象,它提供拦挡 JavaScript 操作的办法。这些办法与proxy handlers(en-US)的办法雷同。

罕用办法的语法

Reflect.set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;Reflect.get(target: object, propertyKey: PropertyKey, receiver?: any): any;

在Vue3中的作用

Reflect的一些办法等价于Object办法的成果

ReflectVue3中用来代替对象的一些办法操作,如上面代码所示,间接拜访obj.xxxReflect.get(obj, "xxx", obj)的成果是一样的,惟一不同点是Reflect的第三个参数,当与Proxy一起应用时,receiver是代理target的对象

const obj = {count: 1}const temp = obj.count; // 1// 下面代码等价于上面的代码const temp1 = Reflect.get(obj, "count", obj); // 1
Reflect的一些办法具备操作的返回值,而Object具备等同成果的办法没有返回值

Reflect.set():如果在对象上胜利设置了属性,则Reflect.set()返回true,否则返回false。如果指标不是Object,则抛出TypeError
Reflect.get():Reflect.get()返回属性的值。如果指标不是Object,则抛出TypeError
Reflect.deleteProperty():如果属性从对象中删除,则Reflect.deleteProperty()返回true,否则返回false

扭转非凡原始对象的this指针问题

从上面的代码能够晓得,当有一些原始Object存在this指针时,如果没有应用Reflect解决this对象时,会导致effect()无奈失常收集依赖的状况
比方上面的代码块,咱们现实中的状态是p1.count1->get count1()->return this.count->打印出9999,然而理论打印确实是22,此时的get count1() {}this指向obj,而不是p1

const obj = {  count: 22,  get count1() {    console.log("count1")    // 此时的this指向obj,而不是p    return this.count;  }}const p = new Proxy(obj, {  get(target, key, receiver) {    // target: 原始对象,此时为obj    // key: 触发的key    // receiver: proxy对象,此时为p    return target[key];  }});const p1 = {  __proto__: p,  count: 9999}console.log(p1.count1); // 22

因而咱们能够建设上面的代码块,现实中,咱们在effect中打印出p.count1,现实状况下,当p.count扭转时,咱们心愿effect从新执行,输入最新的p.count1也就是p.count的值。然而实际上什么都没有产生,因为console.log(p.count1)并不会触发p.count的依赖收集,因为上面代码中理论拜访的是obj.count,而不是p.count,没有依赖收集,p.count天然也不会有派发更新告诉effect从新执行

const obj = {  count: 22,  get count1() {    console.log("count1")    // 此时的this指向obj,而不是p    return this.count;  }}const p = new Proxy(obj, {  get(target, key, receiver) {    // target: 原始对象,此时为obj    // key: 触发的key    // receiver: proxy对象,此时为p    return target[key];  }});effect(()=> {  // 从下面p1的例子能够看出  // 此时的count1中的this指向obj,会导致p.count无奈收集该effect,导致p.count更新时无奈告诉到effect从新执行  console.log(p.count1);//22});onMounted(()=> {  p.count = 66666; // 不会触发下面的effect从新执行});

当应用了Reflect之后,咱们就能够应用receiver参数明确调用主体,把代理对象p当作this的主体,避免很多意外可能性产生(如上面的代码)

const obj = {  count: 22,  get count1() {    return this.count;  }}const p = new Proxy(obj, {  get(target, key, receiver) {    // target: 原始对象,此时为obj    // key: 触发的key    // receiver: proxy对象,此时为p    return Reflect.get(target, key, receiver);  }});const p1 = {  __proto__: p,  count: 9999}console.log(p1.count); // 9999

依赖收集

流程图

根本流程

应用Proxy进行target对象的代理初始化,由上背后置常识可知,proxy会劫持target的多个办法,上面代码中应用mutableHandlersProxyhandler办法

// packages/reactivity/src/reactive.tsfunction reactive(target: object) {    // if trying to observe a readonly proxy, return the readonly version.    if (isReadonly(target)) {        return target    }    return createReactiveObject(        target,        false,        mutableHandlers,        mutableCollectionHandlers,        reactiveMap    )}function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {    //......一系列的解决,包含判断target是否曾经是Proxy,target是不是对象,曾经有proxyMap缓存等等    const proxy = new Proxy(        target,        // TargetType.COLLECTION = Map/Set/WeakMap/WeakSet        targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers    )    return proxy}

从上面代码能够看出,Object类型最终依赖收集的外围办法是track()办法

如果res=target[key]依然是Object,则持续调用reactive()办法进行包裹,因而reactive(Object)中Object的所有key,包含深层的key都具备响应式
最终createGetter()返回的是原始值(Number/String等)或者是一个Proxy(包裹了Object)类型的值
// packages/reactivity/src/baseHandlers.tsconst mutableHandlers: ProxyHandler<object> = {    get, // const get = createGetter()    set,    deleteProperty,    has,    ownKeys}function createGetter(isReadonly = false, shallow = false) {    return function get(target: Target, key: string | symbol, receiver: object) {        //...省略Array的解决,前面再剖析        //...省略isReadonly类型的解决 + shallow类型的解决 + ref数据类型的解决,前面再剖析        const res = Reflect.get(target, key, receiver)        if (!isReadonly) {             track(target, TrackOpTypes.GET, key)         }        if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res) }        return res    }}

track()外围办法

shouldTrack和activeEffect将在下一大节中解说,目前认为shouldTrack就是代表须要收集依赖的标记,activeEffect代表目前正在执行的函数(函数中有响应式数据,触发依赖收集)
function track(target: object, type: TrackOpTypes, key: unknown) {    if (shouldTrack && activeEffect) {        let depsMap = targetMap.get(target)        if (!depsMap) {            targetMap.set(target, (depsMap = new Map()))        }        let dep = depsMap.get(key)        if (!dep) {            // const dep = new Set<ReactiveEffect>(effects) as Dep            // dep.w = 0            // dep.n = 0            depsMap.set(key, (dep = createDep()))        }        trackEffects(dep)    }}

从上面代码块能够晓得,应用dep.n作为标记位,监测是否须要进行依赖收集,如果须要收集,则进行effect和以后响应式对象所持有的dep的相互绑定

  • dep.add(activeEffec)
  • activeEffect.deps.push(dep)
dep.ndep.m以及effectTrackDepth和maxMarkerBits是为了优化 每次执行effect函数都须要先清空所有依赖,而后再收集依赖的流程。因为有些依赖是始终没有变动的,不须要每次都革除,具体分析将放在上面剖析
function trackEffects(dep: Dep) {    let shouldTrack = false    if (effectTrackDepth <= maxMarkerBits) {        if (!newTracked(dep)) {            dep.n |= trackOpBit // set newly tracked            shouldTrack = !wasTracked(dep)        }    } else {        // Full cleanup mode.        shouldTrack = !dep.has(activeEffect!)    }    if (shouldTrack) {        dep.add(activeEffect!)        activeEffect!.deps.push(dep)    }}

派发更新

流程图

根本流程

由下面依赖收集的流程能够晓得,Proxy劫持了对象的set()办法,实际上就是createSetter()办法

// packages/reactivity/src/baseHandlers.tsconst mutableHandlers: ProxyHandler<object> = {    get, // const get = createGetter()    set, // const set = createSetter()    deleteProperty,    has,    ownKeys}

从上面的代码,咱们能够看出,最终createSetter()办法触发的逻辑次要有

  • Reflect.set()进行原始值的数据更新
  • 获取要触发trigger()的类型:判断key是否存在target上,如果不是,前面将应用TriggerOpTypes.SET类型,否则就应用TriggerOpTypes.ADD类型
  • 判断target === toRaw(receiver),解决target以及target.__proto都是Proxy()对象导致拜访target.xxx时触发effect()函数调用两次的问题(在文章最初一大节会剖析,这里先放过这些细节问题)
  • 应用Object.is()判断新旧值是否扭转,包含NaN;只有扭转时才会触发trigger()办法
function createSetter(shallow = false) {    return function set(target: object, key: string | symbol, value: unknown, receiver: object): boolean {        //... 省略ref、shallow、readonly的数据处理逻辑        const hadKey =            isArray(target) && isIntegerKey(key)                ? Number(key) < target.length                : hasOwn(target, key)        const result = Reflect.set(target, key, value, receiver)        if (target === toRaw(receiver)) {            if (!hadKey) {                trigger(target, TriggerOpTypes.ADD, key, value)            } else if (hasChanged(value, oldValue)) {                trigger(target, TriggerOpTypes.SET, key, value, oldValue)            }        }        return result    }}// packages/shared/src/index.tsexport const hasChanged = (value: any, oldValue: any): boolean =>  !Object.is(value, oldValue)

trigger()外围办法

export function trigger(...args) {    const depsMap = targetMap.get(target)    if (!depsMap) {        return    }    let deps: (Dep | undefined)[] = []    // ...省略逻辑:依据TriggerOpTypes类型,比方CLEAR、SET、ADD、DELETE、Map.SET等等去获取对应的deps汇合    if (deps.length === 1) {        if (deps[0]) {            triggerEffects(deps[0])        }    } else {        const effects: ReactiveEffect[] = []        for (const dep of deps) {            if (dep) {                effects.push(...dep)            }        }        triggerEffects(createDep(effects))    }}
function triggerEffects(dep: Dep | ReactiveEffect[]) {    for (const effect of isArray(dep) ? dep : [...dep]) {        if (effect !== activeEffect || effect.allowRecurse) {            if (effect.scheduler) {                // watch类型的effect调用                effect.scheduler()            } else {                // 间接进行effect()办法的从新执行                effect.run()            }        }    }}

从下面代码可知,最终trigger()依据传来的参数,比方减少key更新对应key的数据删除对应的key来拼接对应的deps汇合,而后调用对应的deps所持有的effect数组汇合,比方TriggerOpTypes.ADD

case TriggerOpTypes.ADD:    if (!isArray(target)) {        deps.push(depsMap.get(ITERATE_KEY))        if (isMap(target)) {            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))        }    } else if (isIntegerKey(key)) {        // new index added to array -> length changes        deps.push(depsMap.get('length'))    }break
这种依据type=add/delete/clear/set进行effect汇合的拼凑,波及到Object、Array、Map、Set等多种数据的解决,为了升高本文复杂度,本文不进行开展剖析,将在前面的文章进行多种type+多种类型数据的响应式拦挡剖析

外围ReactiveEffect

为了更好辨别Proxy-key持有的deps(Set对象,存储Effect数组)以及effect.deps(Array对象,存储Proxy-key持有的deps)
上面示例代码将革新原来源码,Proxy-key持有的deps名称改为:effectsSet,代表收集effect汇合
effect.deps改为:depsArray(item是 effectsSet),代表每一个effect持有的响应式对象的deps,能够认为所持有的响应式对象

初始化

将目前须要执行的fnscheduler传入

var ReactiveEffect = class {    constructor(fn, scheduler = null, scope) {        this.fn = fn;        this.scheduler = scheduler;        this.active = true;        this.depsArray = [];        this.parent = void 0;        recordEffectScope(this, scope);    }};

外围源码

run() {    if (!this.active) {        return this.fn();    }    let parent = activeEffect;    let lastShouldTrack = shouldTrack;        while (parent) {    // v3.2.29旧版本代码为上面这一行:    //if (!effectStack.length || !effectStack.includes(this)) {}        if (parent === this) {            return;        }        parent = parent.parent;    }    try {        this.parent = activeEffect;        activeEffect = this;        shouldTrack = true;        return this.fn();    } finally {        activeEffect = this.parent;        shouldTrack = lastShouldTrack;        this.parent = void 0;        if (this.deferStop) {            this.stop();        }    }}

一般effect执行Proxy.set的源码解决

示例

B(effect)触发了proxy.count = new Date().getTime(),会触发A(effect)从新执行,不会触发B(effect)从新执行

// Aeffect(() => {    console.error("测试A:" + proxy.count);});// Beffect(() => {    console.error("测试B:" + proxy.count);    proxy.count = new Date().getTime();});

源码剖析

由下面派发更新的源码流程能够晓得,最终派发更新触发的流程是trigger()->triggerEffects()->ReactiveEffect.run()
从上面源码能够晓得,如果只是简略在effect中执行proxy.count=xxxset操作,那么因为triggerEffects()源码中会进行effect!==activeEffect的判断,会阻止在以后effect进行依赖收集又同时进行依赖更新的事件,因而下面示例中的console.error("测试B:" + proxy.count)被阻止执行

一般effect执行Proxy.set的源码解决不太关ReactiveEffect.run()的事件,这里解说一般effect执行Proxy.set的源码解决是为了上面的嵌套effect铺垫
function triggerEffects(dep, debuggerEventExtraInfo) {    // spread into array for stabilization    for (const effect of isArray(dep) ? dep : [...dep]) {        if (effect !== activeEffect || effect.allowRecurse) {            if (effect.onTrigger) {                effect.onTrigger(extend({ effect }, debuggerEventExtraInfo));            }            if (effect.scheduler) {                effect.scheduler();            }            else {                effect.run();            }        }    }}

嵌套effect 和 在嵌套effect执行Proxy.set的解决

嵌套effect非凡状况示例

为了防止嵌套effect()产生的依赖收集错乱,比方上面示例代码所示,咱们须要将obj.count内层的effect()关联,将obj.count1外层的effect()关联,当obj.count产生扭转时,触发内层effect从新执行一次,外层effect不执行

const obj = new Proxy({count: 1, count1: 22});effect(()=> {  effect(()=> {    console.log(obj.count);  });  console.log(obj.count1);});obj.count=22; // 触发内层effect从新执行一次,外层effect不执行

嵌套effect非凡状况源码剖析

由下面派发更新的源码流程能够晓得,最终派发更新触发的流程是trigger()->triggerEffects()->ReactiveEffect.run()
为了解决嵌套effect,如上面精简源码所示,Vue3ReactiveEffect.run()执行this.fn()前,会将上次的activeEffectshouldTrack状态保留,执行this.fn()结束后,将状态还原到上一个状态,实现一种分支切换的性能

run() {    let parent = activeEffect;    let lastShouldTrack = shouldTrack;    try {        this.parent = activeEffect;        activeEffect = this;        shouldTrack = true;        return this.fn();    } finally {        activeEffect = this.parent;        shouldTrack = lastShouldTrack;        this.parent = void 0;    }}

this.fn()的执行,实际上就是从新执行一遍带有响应式变量的办法,这个时候会触发响应式变量Proxyget()办法,从而触发下面剖析的依赖收集,而后触发外围的track()办法,如上面代码所示,在嵌套effect中的activeEffect就是每一个嵌套子effect,保障了响应式变量依赖收集到正确的effect

trackEffects()办法内也有一个shouldTrack变量->局部变量,track()办法内的shouldTrack变量->全局变量,不要搞混....
function track(target: object, type: TrackOpTypes, key: unknown) {    if (shouldTrack && activeEffect) {        let depsMap = targetMap.get(target)        if (!depsMap) {            targetMap.set(target, (depsMap = new Map()))        }        let dep = depsMap.get(key)        if (!dep) {            // const dep = new Set<ReactiveEffect>(effects) as Dep            // dep.w = 0            // dep.n = 0            depsMap.set(key, (dep = createDep()))        }        trackEffects(dep)    }}

在嵌套effect执行Proxy.set非凡状况解说

如上面两个代码块所示,如果Vue源码不进行解决,比方github调试代码所示,当B(effect)执行Proxy.set()操作时,会触发A(effect)的从新执行,而A(effect)中具备Proxy.set()操作时,又会触发B(effect)的从新执行,从而造成有限递归循环调用

  • 代码块1:proxy.count = proxy.count + 11111111 ===> B(effect)->A(effect)->B(effect)
  • 代码块2:proxy.count = new Date().getTime() ===> B(effect)->A(effect)->B(effect)
// A(effect)effect(() => {    console.error("测试:" + proxy.count);    proxy.count = 5;});// B(effect)effect(() => {    // proxy.count = 111只会触发set,不会触发parent返回逻辑    // proxy.count = proxy.count + 11111111既触发get,也触发set,有进行依赖收集    proxy.count = proxy.count + 11111111;});// proxy.count = proxy.count + 11111111 ===>  B(effect)->A(effect)->B(effect)// proxy.count = 333 =====> B(effect)->A(effect)
// A(effect)effect(() => {    console.error("测试:" + proxy.count);    // B(effect)    effect(() => {        proxy.count = new Date().getTime();    });});// A(effect)->B(effect)->A(effect)

在嵌套effect执行Proxy.set非凡状况源码剖析

由下面派发更新的源码流程能够晓得,最终派发更新触发的流程是trigger()->triggerEffects()->ReactiveEffect.run()
从上面源码能够看出,当咱们执行ReactiveEffect.run()时,会应用this.parent=activeEffect,而后再执行this.fn()
当嵌套effect产生时,上一个嵌套effect就是子effectparent,比方下面示例代码块2中,A(effect)就是B(effect)parent,即B(effect)this.parent=A(effect),因而A(effect)->B(effect)->A(effect)的流程中,会因为parent === this而间接中断整个有限递归的产生

run() {    if (!this.active) {        return this.fn();    }    let parent = activeEffect; // 上一个Effect    let lastShouldTrack = shouldTrack;    while (parent) {        if (parent === this) {            // B(effect).parent=A(effect)            // this=A(effect)            return;        }        parent = parent.parent;    }    try {        this.parent = activeEffect;        //.....         return this.fn();    }}

cleanupEffect()清空effect和优化逻辑

全副革除

为了防止相似v-if/currentStatus?obj.count:obj.data这种切换状态后依赖过期的状况,咱们能够在每次依赖收集时进行依赖的清空,而后再收集依赖
cleanupEffect()提供了革除全副effect的能力,在effectTrackDepth > maxMarkerBits/ ReactiveEffect.stop()时调用

function cleanupEffect(effect) {    const { deps } = effect    if (deps.length) {        for (let i = 0; i < deps.length; i++) {            deps[i].delete(effect)        }        deps.length = 0    }}

全副革除优化

每次执行依赖收集的过程中,都会进行cleanup(),然而在一些场景下,依赖关系是很少变动的,为了缩小每次依赖收集时effect的增加和删除操作,咱们须要标识每一个依赖汇合dep的状态,标识它是新收集的,还是曾经被收集过的,对这种清空依赖的逻辑进行优化
如上面代码所示,咱们为Proxy-key持有的dep(响应式数据持有的effect汇合)减少两个属性dep.wdep.n

  • dep.w:代表在某个递归深度下,是否被收集的标记位
  • dep.h:代表在某个递归深度下,最新状态下是否被收集的标记位
如果dep.w成立,dep.h不成立,阐明该响应式数据在该effect曾经过期,应该删除
  function track(target, type, key) {    if (shouldTrack && activeEffect) {      // ....      let dep = depsMap.get(key);      if (!dep) {        depsMap.set(key, dep = createDep());      }      //...    }  }  // packages/reactivity/src/dep.ts  var createDep = (effects) => {    const dep = new Set(effects);    dep.w = 0;    dep.n = 0;    return dep;  };

initDepMarkers()触发dep.w(effectsSet.w)的构建

咱们在ReactiveEffect.run()中应用三个属性进行递归深度的标识

  • trackOpBit:示意递归嵌套执行effect函数的深度,应用二进制的模式,比方10100100010000
  • effectTrackDepth:示意递归嵌套执行effect函数的深度,应用十进制的模式,比方1234
  • maxMarkerBits:示意递归嵌套的最大深度,默认为30,跟effectTrackDepth进行比拟
为了更好辨别Proxy-key持有的deps(Set对象,存储Effect数组)以及effect.deps(Array对象,存储Proxy-key持有的deps)
上面示例代码将革新原来源码,Proxy-key持有的deps名称改为:effectsSet
effect.deps改为:depsArray
run() {    // ...省略    try {      this.parent = activeEffect;      activeEffect = this;      shouldTrack = true;      trackOpBit = 1 << ++effectTrackDepth;      if (effectTrackDepth <= maxMarkerBits) {        initDepMarkers(this);      } else {        cleanupEffect(this);      }      return this.fn();    } finally {      if (effectTrackDepth <= maxMarkerBits) {        finalizeDepMarkers(this);      }      trackOpBit = 1 << --effectTrackDepth;      activeEffect = this.parent;      shouldTrack = lastShouldTrack;      this.parent = void 0;      if (this.deferStop) {        this.stop();      }    }}

从下面的代码可知,首先触发了initDepMarkers()进行该effect持有的depsArray进行depsArray[i].w |= trackOpBit的标记,其中depsArray[i]就是响应式对象所持有的effect汇合,为了不便了解,咱们看作depsArray[i]就是一个响应式对象,为每一个响应式对象的w属性,进行响应式对象.w |= trackOpBit的标记

var initDepMarkers = ({ depsArray }) => {    if (depsArray.length) {      for (let i = 0; i < depsArray.length; i++) {        // depsArray[i] = effectsSet(每一个target的key进行createDep()创立的Set数组)        // depsArray[i].w = effectsSet.w        depsArray[i].w |= trackOpBit;      }    }};

Effect.run()-this.fn()执行触发Proxy对象的get()申请,从而触发依赖收集,应用dep.n(effectsSet.n)标识最新状况的依赖

为了更好辨别Proxy-key持有的deps(Set对象,存储Effect数组)以及effect.deps(Array对象,存储Proxy-key持有的deps)
上面示例代码将革新原来源码,Proxy-key持有的deps名称改为:effectsSet
effect.deps改为:depsArray
  • 从上面的代码能够晓得,咱们应用effectsSet.n |= trackOpBit进行目前effect所持有的响应式对象的标记,如果最新一轮依赖收集曾经标记过(即newTracked(effectsSet)=true),那就不必标记dep.n了,也不必新增追踪(即shouldTrack2=false
  • 如果没有标记过(即newTracked(effectsSet)=false),那就进行标记,并且判断之前是否曾经收集过shouldTrack2 = !wasTracked(dep)
  • 以上两种是effectTrackDepth <= maxMarkerBits的状况,当超过递归深度时,执行shouldTrack2 = !effectsSet.has(activeEffect),因为超过递归深度,所有effectsSet.w都会生效了(ReactiveEffect.run调用了cleanupEffect()
function track(target, type, key) {    // ...    trackEffects(effectsSet, eventInfo);}function trackEffects(effectsSet, debuggerEventExtraInfo) {    let shouldTrack2 = false;    if (effectTrackDepth <= maxMarkerBits) {        //newTracked=(dep)=>(effectsSet.n & trackOpBit)>0        if (!newTracked(effectsSet)) {            effectsSet.n |= trackOpBit;            shouldTrack2 = !wasTracked(dep);        }    } else {        shouldTrack2 = !effectsSet.has(activeEffect);    }    // ...}

执行Effect.run()-this.fn()实现后,执行finalizeDepMarkers()办法,依据dep.w和dep.n进行过期dep的筛选

从上面代码能够晓得,

  • 如果wasTracked(dep)=true && newTracked(dep)=false,阐明该effect曾经不依赖这个响应式对象了,间接进行响应式对象的dep.delete(effect),这里的dep是响应式对象持有的effect汇合,也就是咱们剖析革新名称的effectsSet
  • 如果下面条件不成立,阐明对于effect()函数来说,这个响应式对象是新增的/之前曾经存在,当初依然须要,则执行deps[ptr++] = dep,这里的depseffect所持有的depsArray
var finalizeDepMarkers = (effect2) => {    const { deps } = effect2;    if (deps.length) {        let ptr = 0;        for (let i = 0; i < deps.length; i++) {            const dep = deps[i];            if (wasTracked(dep) && !newTracked(dep)) {                // wasTracked:var wasTracked = (dep) => (dep.w & trackOpBit) > 0;                // newTracked: var newTracked = (dep) => (dep.n & trackOpBit) > 0;                dep.delete(effect2);            } else {                deps[ptr++] = dep;            }            dep.w &= ~trackOpBit;// 将目前递归深度对应那一个bit置为0            dep.n &= ~trackOpBit;// 将目前递归深度对应那一个bit置为0        }        deps.length = ptr;    }};

ReactiveEffect流程图总结

computed类型响应式剖析

例子

<div id='el'>  {{computedData}}</div><script>  const { effect, onMounted, reactive, createApp, computed } = Vue;  const App = {    setup(props, ctx) {      const obj = {count: 1};      const proxy = reactive(obj);      const computedData = computed(()=> {        return proxy.count+1;      });      return {        computedData      };    },  };  const app = createApp(App);  app.mount("#el");</script>

依赖收集流程图总结

依赖收集流程图源码剖析

computed初始化

  • 判断传入的getter是否是函数,如果传入的是函数,则手动设置一个空的setter()办法
  • 创立ComputedRefImpl实例
function computed(getterOrOptions, debugOptions, isSSR = false) {    let getter;    let setter;    const onlyGetter = isFunction(getterOrOptions);    if (onlyGetter) {        getter = getterOrOptions;        setter = () => {            console.warn('Write operation failed: computed value is readonly');        };    }    else {        getter = getterOrOptions.get;        setter = getterOrOptions.set;    }    const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);    return cRef;}

理论外围ComputedRefImpl初始化

初始化ReactiveEffect,传入getter作为fn,以及设置scheduler

class ComputedRefImpl {    constructor(getter, _setter, isReadonly, isSSR) {        // ...省略代码        this._dirty = true;        this.effect = new ReactiveEffect(getter, () => {            if (!this._dirty) {                this._dirty = true;                triggerRefValue(this);            }        });        this.effect.computed = this;    }    get value() {        const self = toRaw(this);        trackRefValue(self);        if (self._dirty || !self._cacheable) {            self._dirty = false;            self._value = self.effect.run();        }        return self._value;    }}class ReactiveEffect {    constructor(fn, scheduler = null, scope) {        this.fn = fn;        this.scheduler = scheduler;    }}

依赖收集:ComputedRefImpl初始化数据

  • 由下面的示例能够晓得,最终界面会触发ComputedRefImpl.value的获取,触发依赖收集
  • 获取数据时,会触发trackRefValue()进行依赖收集,如果compute data界面渲染effect中,此时的activeEffect就是界面渲染effectComputedRefImpl.dep收集界面渲染effect
function trackRefValue(ref) {    trackEffects(ref.dep || (ref.dep = createDep()));}function trackEffects(effectsSet, debuggerEventExtraInfo) {    let shouldTrack2 = false;    if (effectTrackDepth <= maxMarkerBits) {        //newTracked=(dep)=>(dep.n & trackOpBit)>0        if (!newTracked(effectsSet)) {            effectsSet.n |= trackOpBit;            shouldTrack2 = !wasTracked(dep);        }    } else {        shouldTrack2 = !dep.has(activeEffect);    }    if (shouldTrack) {        dep.add(activeEffect!)        activeEffect!.deps.push(dep)    }}
  • self._dirty置为false,并且触发self.effect.run(),此时会触发this.fn(),即proxy.count+1这个computed初始化时的办法执行,最终run()返回this.fn(),即proxy.count+1的值
  • 拜访proxy.count+1时会触发Proxy的get()办法,从而触发响应式数据的依赖收集,由下面依赖收集的流程剖析能够晓得,会执行track()->trackEffects(),最终将目前的computed effect退出到响应式数据的dep中
  • 此时computed effect的依赖收集完结
run() {    if (!this.active) {        return this.fn();    }    let parent = activeEffect;    let lastShouldTrack = shouldTrack;        while (parent) {    // v3.2.29旧版本代码为:    //if (!effectStack.length || !effectStack.includes(this)) {}        if (parent === this) {            // 如果在嵌套effect中发现之前曾经执行过目前的effect            // 则阻止执行,防止有限嵌套执行            return;        }        parent = parent.parent;    }    try {        this.parent = activeEffect;        activeEffect = this;        shouldTrack = true;        return this.fn();    } finally {        activeEffect = this.parent;        shouldTrack = lastShouldTrack;        this.parent = void 0;        if (this.deferStop) {            this.stop();        }    }}

派发更新流程图

派发更新流程图源码剖析

ComputedRefImpl数据变动

  • 如果computed effect外面的响应式数据发生变化,由下面派发更新的剖析能够晓得,会触发trigger->triggerEffects,最终触发effect.scheduler()的执行
function trigger(target, type, key, newValue, oldValue, oldTarget) {    //...省略deps的拼接判断逻辑    const effects = [];    for (const dep of deps) {        if (dep) {            effects.push(...dep);        }    }    triggerEffects(createDep(effects), eventInfo);}function triggerEffects(dep, debuggerEventExtraInfo) {    for (const effect of isArray(dep) ? dep : [...dep]) {        if (effect !== activeEffect || effect.allowRecurse) {            if (effect.scheduler) effect.scheduler();            else effect.run();        }    }}
  • 即上面的代码,将this._dirty置为true,手动触发triggerRefValue->triggerEffects,此时触发的是ComputedRefImpl.dep的effects,也就是computed对象收集的渲染effects执行
  • 渲染effect从新执行,会从新触发响应式对象的get()办法,即拜访computed.value
this.effect = new ReactiveEffect(getter, () => {    if (!this._dirty) {        this._dirty = true;        triggerRefValue(this);    }});function triggerRefValue(ref, newVal) {    ref = toRaw(ref);    if (ref.dep) {      // 下面依赖收集的ComputedRefImpl.dep对象      // 收集的是渲染effect        triggerEffects(ref.dep);    }}function triggerEffects(dep: Dep | ReactiveEffect[]) {    for (const effect of isArray(dep) ? dep : [...dep]) {        if (effect !== activeEffect || effect.allowRecurse) {            if (effect.scheduler) {                effect.scheduler()            } else {                // 间接进行effect()办法的从新执行                effect.run()            }        }    }}

从上面的代码可知,当拜访get value()时,因为曾经将self._dirty置为true,因而后续的流程跟下面剖析的computed依赖收集的流程统一,最终返回新的计算的effect.run()的值

class ComputedRefImpl {    constructor(getter, _setter, isReadonly, isSSR) {        // ...省略代码        this._dirty = true;        this.effect = new ReactiveEffect(getter, () => {            if (!this._dirty) {                this._dirty = true;                triggerRefValue(this);            }        });        this.effect.computed = this;    }    get value() {        const self = toRaw(this);        trackRefValue(self);        if (self._dirty || !self._cacheable) {            self._dirty = false;            self._value = self.effect.run();        }        return self._value;    }}

computed依赖数据没有变动

由上面代码能够发现,当第一次渲染实现,调用.value数据后,self.dirty会置为false,因而当computed依赖的数据没有变动时,会始终返回之前计算出来的self._value,而不是触发从新计算self.effect.run()

class ComputedRefImpl {    get value() {        const self = toRaw(this);        trackRefValue(self);        if (self._dirty || !self._cacheable) {            self._dirty = false;            self._value = self.effect.run();        }        return self._value;    }}

watch类型响应式剖析

初始化

整体概述

function watch(source, cb, options) {    return doWatch(source, cb, options);}function watchEffect(effect, options) {    return doWatch(effect, null, options);}
  • 拼接getter数据,将传入的数据、refreactive进行整顿造成标准的数据(下文会详细分析)
  • cleanup:注册革除办法,在watchEffectsource触发执行/watch监听值扭转时,会暴露出一个onCleanup()办法,咱们能够通过onCleanup传入一个fn()办法,在适当的时机会调用革除目前的监听办法(下文会详细分析)
  • 初始化job(): 依据cb构建回调,次要是依据oldValuenewValue进行回调(下文会详细分析)
  • 依据flush('pre'、'sync'、'post')构建scheduler(下文会详细分析)
  • 初始化new ReactiveEffect(getter, scheduler)
  • 初始化运行:依据传入的参数

    • 如果是watch类型,并且是immediate立即执行job(),立即触发cb(newValue, undefined)回调,而后更新oldValue=newValue
    • 如果是watch类型,immediate=false,则计算结果缓存oldValue
    • 如果不是watch类型(还有watchEffectwatchPostEffectwatchSyncEffect),并且flush === 'post',也就是watchPostEffect时,不立即执行effect.run(),而是推入post队列延后执行
    • 如果是watchEffect类型,初始化就会执行一次,间接运行effect.run()
  • 返回function:调用能够立即进行watch监听,会同时调用下面通过onCleanup(fn)注册的办法fn()
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ) {    // 初始化job(): 依据cb构建回调,次要是依据oldValue和newValue进行回调    let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE;    const job = () => {        // ...改写代码,只展现外围局部        if (cleanup) { cleanup(); }        const newValue = effect.run();        let currentOldValue = oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue;        // 最终调用 args = [newValue, oldValue, onCleanup]          // res = args ? cb(...args) : cb();        callWithAsyncErrorHandling(cb, instance, 3, [            newValue,            currentOldValue            onCleanup        ]);        oldValue = newValue;    }    // ...拼接getter数据    // ...省略代码,依据flush('pre'、'sync'、'post')构建scheduler    const effect = new ReactiveEffect(getter, scheduler);    // initial run    if (cb) {        if (immediate) job();        else oldValue = effect.run();    } else if (flush === 'post') {        queuePostRenderEffect(effect.run.bind(effect), instance && instance.suspense);    } else {        effect.run();    }    return () => {        effect.stop();    };}

getter构建流程剖析

如果是ref数据,则解构出source.value值,监测是否是isShallow,如果是的话,则forceTrigger=true

isShallow类型相干剖析将在前面文章对立介绍
if (isRef(source)) {    getter = () => source.value;    forceTrigger = isShallow(source);}

如果是reactive数据,则触发深度遍历,为所有key都进行依赖手机,目前的activeEffectwatch类型的effect

else if (isReactive(source)) {    getter = () => source;    deep = true;}

如果是function类型,须要判断目前是watch/watchEffect类型的effect

通过cb是否存在来辨别watch/watchEffect类型
  • watch: getter()就是source()的执行,也就是watch(()=>count.value, cb)中的 count.value
  • watchEffectgetter()就是watchEffect(fn)中的fn办法,从上面源码能够晓得,一开始会执行fn(onCleanup)
如果watch间接监听ref/reactive,会进行.value/深度遍历object,如果咱们想要只监听一个对象的某一个深度属性,咱们能够间接应用function类型,间接watch(()=> obj.count, cb),那么依赖最终只会收集obj.count,而不会整个obj都进行依赖收集
else if (isFunction(source)) {    if (cb) {        // getter with cb        getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */);    }else {        // no cb -> simple effect        getter = () => {            // ...            if (cleanup) {                cleanup();            }            // 最终调用 args = [onCleanup]            // callWithAsyncErrorHandling => res = source(...[onCleanup]);            return callWithAsyncErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onCleanup]);        };    }}

如果是数组数据,即有多个响应式对象时,须要判断是否是refreactivefunction类型,而后依据下面3种进行解决,getter()失去的就是一个解决好的数组状况,比方[count.value, Proxy(object), wactch中监听的source, watchEffect(onCleanup)]

function类型: watch(()=>count.value, ()=> {})中,()=>count.value就是function类型
else if (isArray(source)) {    isMultiSource = true;    forceTrigger = source.some(isReactive);    getter = () => source.map(s => {        if (isRef(s)) {            return s.value;        }        else if (isReactive(s)) {            return traverse(s);        }        else if (isFunction(s)) {            return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */);        }        else {            warnInvalidSource(s);        }    });}

job()构建流程剖析

  • 依据effect.active判断目前的effect的运行状态
  • 依据是否有cb判断是失常的watch还是watchEffect,如果是watch,间接effect.run()获取最新的值
  • 如果是watchEffect,间接运行effect.run(),即watchEffect(()=> {xxx})传入的函数执行
const job = () => {    if (!effect.active) {        return;    }    if (cb) {        // watch(source, cb)        const newValue = effect.run();        if (deep || forceTrigger ||             (isMultiSource ?                 newValue.some((v, i) => hasChanged(v, oldValue[i]))                : hasChanged(newValue, oldValue)            )        ) {            // cleanup before running cb again            if (cleanup) {                cleanup();            }            callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [                newValue,                // pass undefined as the old value when it's changed for the first time                oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,                onCleanup            ]);            oldValue = newValue;        }    }    else {        // watchEffect        effect.run();    }};

scheduler构建流程剖析

从上面代码能够总结出,一共有三种调度模式

  • sync:同步执行,数据变动时同步执行回调函数
  • post:调用queuePostRenderEffect运行job,在组件更新后才执行
  • pre:判断目前是否曾经isMounted,如果曾经isMounted=true,则调用queuePreFlushCb(job);如果还没实现mounted,则间接运行,即第一次在组件挂载之前执行回调函数,之后更新数据则在组件更新之前调用执行函数
if (flush === 'sync') {    scheduler = job; // the scheduler function gets called directly} else if (flush === 'post') {    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);} else {    // default: 'pre'    scheduler = () => {        if (!instance || instance.isMounted) {            queuePreFlushCb(job);        }        else {            // with 'pre' option, the first call must happen before            // the component is mounted so it is called synchronously.            job();        }    };}
queuePostRenderEffect&queuePreFlushCb

依据pre/post存入不同的队列中,而后触发queueFlush()

function queuePreFlushCb(cb) {    queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex);}function queuePostFlushCb(cb) {    queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex);}function queueCb(cb, activeQueue, pendingQueue, index) {    if (!isArray(cb)) {        if (!activeQueue ||            !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)) {            pendingQueue.push(cb);        }    }    else {        // if cb is an array, it is a component lifecycle hook which can only be        // triggered by a job, which is already deduped in the main queue, so        // we can skip duplicate check here to improve perf        pendingQueue.push(...cb);    }    queueFlush();}function queueFlush() {    if (!isFlushing && !isFlushPending) {        isFlushPending = true;        currentFlushPromise = resolvedPromise.then(flushJobs);    }}
flushJobs
  • 先执行flushPreFlushCbs所有的办法,执行结束后查看是否还有新的办法插入,从新调用一次flushPreFlushCbs
  • 而后执行queue所有的办法
  • 最初执行flushPostFlushCbs所有的办法,而后查看一遍是否有新的item,如果有的话,从新登程一次flushJobs,不是从新调用一次flushPostFlushCbs
flushPreFlushCbs

一直遍历pendingPreFlushCbs列表的job,并触发执行,而后查看一遍是否有新的item增加到pendingPreFlushCbs,一直循环直到所有pendingPreFlushCbs的办法都执行结束

function flushPreFlushCbs(seen, parentJob = null) {    if (pendingPreFlushCbs.length) {        currentPreFlushParentJob = parentJob;        activePreFlushCbs = [...new Set(pendingPreFlushCbs)];        pendingPreFlushCbs.length = 0;        seen = seen || new Map();        for (preFlushIndex = 0; preFlushIndex < activePreFlushCbs.length; preFlushIndex++) {            if (checkRecursiveUpdates(seen, activePreFlushCbs[preFlushIndex])) {                continue;            }            activePreFlushCbs[preFlushIndex]();        }        activePreFlushCbs = null;        preFlushIndex = 0;        currentPreFlushParentJob = null;        // recursively flush until it drains        flushPreFlushCbs(seen, parentJob);    }}
queue执行
// 父子Component的job()进行排序,父Component先执行渲染queue.sort((a, b) => getId(a) - getId(b));const check = (job) => checkRecursiveUpdates(seen, job);try {    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {        const job = queue[flushIndex];        if (job && job.active !== false) {            if (true && check(job)) {                continue;            }            // console.log(`running:`, job.id)            callWithErrorHandling(job, null, 14 /* SCHEDULER */);        }    }}
flushPostFlushCbs
  • 执行一遍pendingPostFlushCbs列表的job,并触发执行,而后查看一遍是否有新的item,如果有的话,从新登程一次flushJobs(因为pre的优先级是最高的,如果因为执行pendingPostFlushCbs产生了pre等级的job,那么也应该先执行该job)
finally {    flushIndex = 0;    queue.length = 0;    flushPostFlushCbs(seen);    isFlushing = false;    currentFlushPromise = null;    // some postFlushCb queued jobs!    // keep flushing until it drains.    if (queue.length ||        pendingPreFlushCbs.length ||        pendingPostFlushCbs.length) {        flushJobs(seen);    }}

cleanup()革除

onCleanup传入fn,进行cleanup初始化
let cleanup;let onCleanup = (fn) => {    cleanup = effect.onStop = () => {        callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);    };};
watchEffect

初始化getter时,默认传入[onCleanup]进行初始化,因为watchEffect一开始就会调用一次getter,也就是会执行一次watchEffect(fn1)fn1,咱们能够在fn1中拿到onCleanup办法,传入fn

// no cb -> simple effectgetter = () => {    if (instance && instance.isUnmounted) {        return;    }    if (cleanup) {        cleanup();    }    return callWithAsyncErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onCleanup]);};

watchEffect初始化时,咱们能够手动调用onCleanup(()=> {})传入fn()

watchEffect((onCleanup)=> {  onCleanup(()=> {    // 这是革除办法fn  });});
watch

初始化job()时,会在回调函数中进行onCleanup的裸露

const job = () => {    if (cb) {        const newValue = effect.run();        if (deep || forceTrigger || (isMultiSource                ? newValue.some((v, i) => hasChanged(v, oldValue[i]))                : hasChanged(newValue, oldValue))) {            // cleanup before running cb again            if (cleanup) {                cleanup();            }            callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [                newValue,                // pass undefined as the old value when it's changed for the first time                oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,                onCleanup            ]);            oldValue = newValue;        }    }};

watch初始化时,咱们能够在cb回调函数中手动调用onCleanup(()=> {})传入fn()

const proxy = reactive({count: 1});const testWatch = watch(proxy, (newValue, oldValue, onCleanup)=> {    onCleanup(()=> {        console.warn("watch onCleanup fn");    });});
cleanup执行机会
watchEffect

每次响应式数据发生变化时,会触发ReactiveEffect.scheduler()->job()->ReactiveEffect.run()->this.fn()->watchEffect getter()
如上面的代码所示,在触发watchEffect(fn)fn之前会先调用一次cleanup()

// no cb -> simple effectgetter = () => {    if (instance && instance.isUnmounted) {        return;    }    if (cleanup) {        cleanup();    }    return callWithAsyncErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onCleanup]);};
watch

每次响应式数据发生变化时,会触发ReactiveEffect.scheduler()->job()
如上面的代码所示,在触发cb(newValue, oldValue, onCleanup)回调前会先调用一次cleanup()

const job = () => {    if (cb) {        const newValue = effect.run();        if (deep || forceTrigger || (isMultiSource                ? newValue.some((v, i) => hasChanged(v, oldValue[i]))                : hasChanged(newValue, oldValue))) {            // cleanup before running cb again            if (cleanup) {                cleanup();            }            callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [                newValue,                // pass undefined as the old value when it's changed for the first time                oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,                onCleanup            ]);            oldValue = newValue;        }    }
effect.stop()

在进行watch监听时触发effect.stop()->effect.onStop()->cleanup()

// doWatch初始化let cleanup;let onCleanup = (fn) => {    cleanup = effect.onStop = () => {        callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);    };};// Reactive.stop()stop() {    if (this.active) {        cleanupEffect(this);        if (this.onStop) {            this.onStop();        }        this.active = false;    }}

依赖收集

派发更新

其它细节剖析

createSetter中target === toRaw(receiver)

如上面代码所示,Proxyset()办法中存在着target === toRaw(receiver)的判断

function createSetter(shallow = false) {    return function set(target: object, key: string | symbol, value: unknown, receiver: object): boolean {        //... 省略ref、shallow、readonly的数据处理逻辑        const hadKey = isArray(target) && isIntegerKey(key)            ? Number(key) < target.length            : hasOwn(target, key)        const result = Reflect.set(target, key, value, receiver)        if (target === toRaw(receiver)) {            if (!hadKey) {                trigger(target, TriggerOpTypes.ADD, key, value)            } else if (hasChanged(value, oldValue)) {                trigger(target, TriggerOpTypes.SET, key, value, oldValue)            }        }        return result    }}// packages/shared/src/index.tsexport const hasChanged = (value: any, oldValue: any): boolean =>    !Object.is(value, oldValue)
  • target === toRaw(receiver)的场景如上面代码所示,在effect()中,会触发proxy.countget()办法,因为proxy没有count这个属性,因而会去拜访它的prototype,即baseProxy
  • 因为proxybaseProxy都是响应式对象,因而会触发proxybaseProxy两个对象的dep收集effect
  • proxy.count扭转的时候,触发Reflect.set()办法,因而会触发proxy收集的effct从新执行一次
  • 然而因为proxyprototype才有count,因而proxy.count扭转的时候,会触发baseProxy.countset()办法执行,从而触发baseProxy收集的effct从新执行一次
  • 由下面的剖析能够晓得,如果没有target === toRaw(receiver)proxy.count扭转最终会触发effect()外部从新执行两次
const obj = {origin: "我是origin"};const objProto = {count: 1, value: "Proto"};const proxy = reactive(obj);const baseProxy = reactive(objProto);Object.setPrototypeOf(proxy, baseProxy);effect(()=> {  // 须要把vue.global.js的createSetter()的target === toRaw(receiver)正文掉,而后就会发现触发了effect两次执行  console.error("测试:"+proxy.count);})onMounted(()=> {  setTimeout(()=> {    proxy.count = new Date().getTime(); // 触发下面effec执行两次  }, 2000);});

为了解决下面这种effect()外部从新执行两次的问题,Vue3应用了target === toRaw(receiver)的形式,次要流程是

  • createSetter()减少了__v_raw的判断,因为执行toRaw()时,实质是获取key === "__v_raw",这个时候就间接返回没有进行Proxy代理前的原始对象obj
function createSetter(shallow = false) {    if (key === "__v_raw" /* RAW */ &&         receiver === (isReadonly2 ? shallow ? shallowReadonlyMap : readonlyMap :                       shallow ? shallowReactiveMap : reactiveMap).get(target)) {        return target;    }}

减少target === toRaw(receiver)后,咱们在vue.config.js减少打印调试数据

const result = Reflect.set(target, key, value, receiver)if (target === toRaw(receiver)) {    console.info("target", target);    console.info("receiver", receiver);    if (!hadKey) {        trigger(target, "add" /* ADD */, key, value);    }    else if (hasChanged(value, oldValue)) {        trigger(target, "set" /* SET */, key, value, oldValue);    }} else {    console.warn("target", target);    console.warn("receiver", receiver);    console.warn("toRaw(receiver)", toRaw(receiver));}

最终打印后果如上面所示,咱们能够发现

  • 先触发了proxy.count->Reflect.set(target, key, value, receiver),此时target=objreceiver=proxy
  • 因为target没有这个count属性,因而触发target的原型获取count属性,也就是

    • proxy.count->Reflect.set(target, key, value, receiver),此时target=objreceiver=proxy
    • ======>触发了
    • Reflect.set(target, key, value, receiver),此时target=objProtoreceiver=proxytoRaw(receiver)=obj,此时因为target不等于toRaw(receiver)而阻止了trigger派发更新逻辑
    • 新的值曾经胜利设置,而后返回return result
    • ======>
    • 持续刚开始的proxy.count->Reflect.set(target, key, value, receiver),此时target=objreceiver=proxytoRaw(receiver)=obj=>胜利触发trigger()
warn: target {count: 1, value: 'Proto'}warn: receiver Proxy {origin: '我是origin', count: 1670680524851}warn: toRaw(receiver) {origin: '我是origin', count: 1670680524851}info: target {origin: '我是origin', count: 1670680524851}info: receiver Proxy {origin: '我是origin', count: 1670680524851}// const obj = {origin: "我是origin"};// const objProto = {count: 1, value: "Proto"};// const proxy = reactive(obj);// const baseProxy = reactive(objProto);// Object.setPrototypeOf(proxy, baseProxy);

外围ReactiveEffect类为什么须要标识递归深度

存在上面一种嵌套拜访同一个obj.count的状况

const obj = reactive({count: 1, count1: 22});effect(()=> {  console.log(obj.count);  effect(()=> {    console.log(obj.count);  });})
  • 由下面外围ReactiveEffect的剖析能够晓得,最终effect.run()会执行finalizeDepMarkers进行依赖的最终整顿
  • 下面例子中最外层的effect()拜访到obj.count时,会触发obj.count收集最外层的effect
  • 当内层的effect()拜访到obj.count时,因为trackOpBit曾经左移一位,对于同一个响应式对象来说,内层effect执行触发依赖收集的trackOpBit跟外层effect执行触发依赖收集的trackOpBit值是不同的,因而当有一个effect的状态发生变化,比方内层effect有一个v-if导致obj.count不必再收集内层effect时,内外effecttrackOpBit值是不同,因而内层effect不会影响外层effect的依赖收集(无论是effectsSet.w还是effectsSet.n对于内外层effect来说都是独立的)
function track(target, type, key) {    // ...    let effectsSet = depsMap.get(key);    if (!effectsSet) {      depsMap.set(key, effectsSet = createDep());    }    trackEffects(effectsSet, eventInfo);}function trackEffects(effectsSet, debuggerEventExtraInfo) {    let shouldTrack2 = false;    if (effectTrackDepth <= maxMarkerBits) {          //newTracked = (dep)=>(dep.n & trackOpBit)>0        if (!newTracked(effectsSet)) {            effectsSet.n |= trackOpBit;            //wasTracked = (dep)=>(dep.w & trackOpBit)>0            shouldTrack2 = !wasTracked(dep);        }    } else {        shouldTrack2 = !dep.has(activeEffect);    }    // ...}

参考文章

  1. Proxy是代理,Reflect是干嘛用的?

Vue系列其它文章

  1. Vue2源码-响应式原理浅析
  2. Vue2源码-整体流程浅析
  3. Vue2源码-双端比拟diff算法 patchVNode流程浅析
  4. Vue3源码-响应式零碎-依赖收集和派发更新流程浅析
  5. Vue3源码-响应式零碎-Object、Array数据响应式总结
  6. Vue3源码-响应式零碎-Set、Map数据响应式总结
  7. Vue3源码-响应式零碎-ref、shallow、readonly相干浅析
  8. Vue3源码-整体流程浅析
  9. Vue3源码-diff算法-patchKeyChildren流程浅析