本文基于Vue 3.2.30
版本源码进行剖析
为了减少可读性,会对源码进行删减、调整程序、扭转的操作,文中所有源码均可视作为伪代码
因为ts版本代码携带参数过多,大部分伪代码会采取js的模式展现,而不是原来的ts代码
本文重点关注依赖收集和派发更新的流程,为了简化流程,会以Object
为根底剖析响应式的流程,不会拘泥于数据类型不同导致的依赖收集和派发更新逻辑不同
本文内容
Proxy
和Reflect
的介绍- 整体依赖收集的流程图以及针对流程图的相干源码剖析
- 整体派发更新的流程图以及针对流程图的相干源码剖析
ReactiveEffect
外围类的相干源码剖析和流程图展现computed
的相干剖析,分为流程图和针对流程图的相干源码剖析watch
和watchEffect
的初始化源码剖析,包含各种配置参数以及schedule
watch
和watchEffect
依赖收集和派发更新流程图展现
每一个点都有配套的流程图,源码剖析是为流程图服务,联合流程图看源码剖析成果更佳
前置常识
在Vue2源码-响应式原理浅析的文章剖析中,咱们晓得Vue2
应用的是Object.defineProperty
的形式,而在Vue3
中应用Proxy
进行代替数据的响应式劫持,上面将简略介绍Vue3
中所应用的Proxy
和Reflect
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办法的成果
Reflect
在Vue3
中用来代替对象的一些办法操作,如上面代码所示,间接拜访obj.xxx
与Reflect.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,则抛出TypeErrorReflect.get()
:Reflect.get()返回属性的值。如果指标不是Object,则抛出TypeErrorReflect.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
的多个办法,上面代码中应用mutableHandlers
为Proxy
的handler
办法
// 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.n
和dep.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
,能够认为所持有的响应式对象
初始化
将目前须要执行的fn
和scheduler
传入
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=xxx
的set
操作,那么因为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,如上面精简源码所示,Vue3
在ReactiveEffect.run()
执行this.fn()
前,会将上次的activeEffect
和shouldTrack
状态保留,执行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()
的执行,实际上就是从新执行一遍带有响应式变量的办法,这个时候会触发响应式变量Proxy
的get()
办法,从而触发下面剖析的依赖收集
,而后触发外围的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
就是子effect
的parent
,比方下面示例代码块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.w
和dep.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
函数的深度,应用二进制的模式,比方10
、100
、1000
、10000
- effectTrackDepth:示意递归嵌套执行
effect
函数的深度,应用十进制的模式,比方1
、2
、3
、4
- 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
,这里的deps
是effect
所持有的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
就是界面渲染effect
,ComputedRefImpl.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
数据,将传入的数据、ref
、reactive
进行整顿造成标准的数据(下文会详细分析) cleanup
:注册革除办法,在watchEffect
的source
触发执行/watch
监听值扭转时,会暴露出一个onCleanup()
办法,咱们能够通过onCleanup
传入一个fn()
办法,在适当的时机会调用革除目前的监听办法(下文会详细分析)- 初始化
job()
: 依据cb
构建回调,次要是依据oldValue
和newValue
进行回调(下文会详细分析) - 依据
flush('pre'、'sync'、'post')
构建scheduler
(下文会详细分析) - 初始化
new ReactiveEffect(getter, scheduler)
初始化运行:依据传入的参数
- 如果是
watch
类型,并且是immediate
立即执行job()
,立即触发cb(newValue, undefined)
回调,而后更新oldValue
=newValue
- 如果是
watch
类型,immediate=false
,则计算结果缓存oldValue
- 如果不是
watch
类型(还有watchEffect
、watchPostEffect
、watchSyncEffect
),并且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
都进行依赖手机,目前的activeEffect
是watch类型的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
watchEffect
:getter()
就是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]); }; }}
如果是数组数据,即有多个响应式对象时,须要判断是否是ref
、reactive
、function
类型,而后依据下面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)
如上面代码所示,Proxy
的set()
办法中存在着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.count
的get()
办法,因为proxy
没有count
这个属性,因而会去拜访它的prototype
,即baseProxy
- 因为
proxy
和baseProxy
都是响应式对象,因而会触发proxy
和baseProxy
两个对象的dep
收集effect
- 当
proxy.count
扭转的时候,触发Reflect.set()
办法,因而会触发proxy
收集的effct
从新执行一次 - 然而因为
proxy
的prototype
才有count
,因而proxy.count
扭转的时候,会触发baseProxy.count
的set()
办法执行,从而触发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=obj
,receiver=proxy
因为
target
没有这个count
属性,因而触发target
的原型获取count
属性,也就是proxy.count
->Reflect.set(target, key, value, receiver)
,此时target=obj
,receiver=proxy
- ======>触发了
Reflect.set(target, key, value, receiver)
,此时target=objProto
,receiver=proxy
,toRaw(receiver)=obj
,此时因为target
不等于toRaw(receiver)
而阻止了trigger
派发更新逻辑- 新的值曾经胜利设置,而后返回
return result
- ======>
- 持续刚开始的
proxy.count
->Reflect.set(target, key, value, receiver)
,此时target=obj
,receiver=proxy
,toRaw(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
时,内外effect
的trackOpBit
值是不同,因而内层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); } // ...}
参考文章
- Proxy是代理,Reflect是干嘛用的?
Vue系列其它文章
- Vue2源码-响应式原理浅析
- Vue2源码-整体流程浅析
- Vue2源码-双端比拟diff算法 patchVNode流程浅析
- Vue3源码-响应式零碎-依赖收集和派发更新流程浅析
- Vue3源码-响应式零碎-Object、Array数据响应式总结
- Vue3源码-响应式零碎-Set、Map数据响应式总结
- Vue3源码-响应式零碎-ref、shallow、readonly相干浅析
- Vue3源码-整体流程浅析
- Vue3源码-diff算法-patchKeyChildren流程浅析