一 概念介绍
1 efn/依赖/watcher:本质上就是 副作用函数:执行后会 影响其他函数执行结果
2 响应式数据:当数据A 发生变化后,可以自动让读取它的函数被自动重新执行,那么A就称作是一个 响应式数据
即 t1.key1值发生变化==> 读取key1的efns自动执行
3 bucket/dep: 结构为一个WeakMap: t1 <–> depsMap
key1 <–> Set(efn1/ efn2 ….),记做deps
它的作用是 记录了 t1和 efn1之间 多对多的映射关系
使用 WeakMap而非Map的原因,其实是为了更高效的进行垃圾回收,节约内存使用
4 efn.deps/watcher.deps属性 + cleanUp(): 用于清理过期的依赖关联 + 避免添加重复的efn
5 activeEffect/Dep.target + effectStack: 用于处理嵌套effect的情况(即 组件嵌套渲染)
二 Vue3的响应式数据的 实现原理
1 renderWatcher的执行原理
S1 收集依赖 + 触发依赖
1 effect(fn1)
2 effectFn(),记做efn1
3.1 cleanUp(efn1):
3.2 activeEffect = efn1 + effectStack.push(efn1)
3.3 Fn()
4.1 读取p1.key1==> p1.key1.get()
5.1 track(t1, key1)
6.1 建立 t1- key1- deps.add(efn1)的关联关系
6.2 让activeEfn(即 efn1).deps收集到 efn1所属的所有keyXDeps
5.2 return t1[key1]
4.2 更新p1.key1的值==> p1.key1.set()
5.1 t1[key1] = newVal
5.2 trigger(t1, key1):获取到 depsMap->effects==> 执行efnX()/efnX.options.scheduler(efnX)
3.4 effectStack.pop() + activeEffect = effectStack[effectStack.length-1]
3.1 cleanUp(efn1)的作用:每次执行efn1时,先删除它之前所有的keyXDeps的依赖关系,从而【清理过期的key-efn】关联,新的关联关系会在 fn()==>track(t1, keyX)里重新构建
3.2 activeEffect + effectStack的作用:防止嵌套efn执行时,activeEfn一直指向内部efn的情况
4.2–> 5.2实现注意点:
要解决 efn1内 data.key的自增操作导致的 无限递归情况,解决方法是 在keyEffects中筛选出不是activeEffect的 efns来执行;利用efnX.options.scheduler,可以实现对efn执行的精确控制,比如 执行时机、执行次数等
2 computed的实现原理
S1 收集依赖
1 effect(fn2)
2 return efn2/ efn2()
3.1 cleanUp(efn2)
3.2 activeEfn = efn2
3.3 res = fn2()==> 读取comp1.value
4.1 comp1._value = copmEfn1()
5.1 cleanUp(compEfn1)
5.2 active = compEfn1
5.3 res = getter()==> track(p1, key1, efn1): 建立p1.key1和compEfn1关联
5.4 重置activeEfn = efn2
4.2 dirty锁开启
4.3 track(comp1, 'value'): 建立comp1.value和efn2的关系
3.4 重置activeEfn
3.5 返回res
S2 触发依赖
1 p1.key1的更新/setter()
2 trigger(p1, key1)
3 compEfn1.scheduler()
4.1 关闭 compEfn1.dirty锁
4.2 trigger(copm1, 'value'): 触发comp1.value的enfX==> efn2()执行...
1 通过effect(options.lazy) + 内部resObj实现了 计算属性特点1: 懒计算
2 通过computed内部定义的dirty标志位,来实现 计算属性的特点2: 值缓存
3 通过effect(options.scheduler),来实现 计算属性【值缓存】的正确值更新
4 通过 track(comp1, ‘value’) + trigger(copm1, ‘value’) 来实现计算属性的 efn嵌套
3 watch的实现原理
S1 收集依赖
1 watch(source1, cb1)
2.1 定义 getter = source1/ traverse
2.2 定义 efn1 = effect(getter, options)
3 return efn1(因为options.lazy为true)
2.3 oldValue = enf1()
3.1 cleanUp(efn1)
3.2 active = efn1
3.3 res = fn(),即 getter()
4 读取p1/ p1.key1的值==> track(t1, key1, efn1)
S2 触发依赖
1 p1.key1的更新/setter()
2 trigger(p1, key1)==> efn1.scheduler()
3.1 newVal = enf1() ==> cleanUp() + .... res = fn()/ getter()
3.2 cb(newVal, oldVal)
3.3 oldVal = newVal(更新oldVal值)
三 Vue3的响应式数据的 语法细节【选读即可】
1 get/set拦截内引入Reflect的原因
S1 effect(fn)
2 efn1()
3 cleanUp()... res= fn()==> 读取了p1.key1
4 track(t1, key1, efn1) + key1是1个访问器属性:return this.key2
S2 this.key2:
1 this指向t1, 则不会触发proxy的getter拦截
2 this指向p1, 才能触发proxy的getter拦截==> 因此要使用Reflect.* 替换 target[key]
2 Proxy代理 对象类型的注意点
1 针对3种读取对象的方法,设置getter
- 属性访问: t1.key1 ==> get拦截 get(t1, key1, receiver)
- in操作符: key1 in t1 ==> has拦截 has(t1, key1)
- for…in循环: for (let key in t1)
- 依赖收集: ownKeys拦截 + track(t1, ite_key, ‘add/set’)
- 依赖触发: ite_keyEfns = depsmap.get(ite_key),执行前提是 add了【新】属性
2 删除对象属性==>
- 依赖收集:deleteProperty(t1, key1)拦截 + tirgger(t1, key, ‘delete’)
- 依赖触发:当类型为add/delete时,获取ite_keyEfns (因为删除操作,会影响 ite_keyEfns)
3 优化处理 读取/设置原型对象上的属性
S1 fn1()==> child.key1的get(t1, k1)
2. track(t1, k1, efn1) + return Reflect.get(t1, k1)
3. k1在t1上不存在,向上查找t1的原型
4. parent.k1的get(t2, k1): track(t2, k1, efn1)
S2 child.k1的 set(t1, k1, newVal)
2.1 oldVal = t1.k1--> t2.k1
2.2 res = t1.k1的 Reflect.set==> t2.k1的set
3.1 oldVal
3.2 receiver.raw读取==> p1.raw的get==> 返回t1
3.3 判断t2是否为 receiver.raw,相等才触发trigger
2.3 t1 === receiver.raw ==> trigger(t1, k1, type)
4 浅响应和深响应
S1 reactive(t1)
2. return createReactive(t1)
3. res = fn1()==> p1.k1.k1_1==> get(t1, k1)
4. track(t1, k1, efn1) + reactive(res1)
5. 递归调用 reactive(res1, k1_1): track(res1, k1_1)
5 只读和浅只读,略
3 Proxy代理 数组类型的注意点
1 数组的索引和length
S1 effect(fn1, options1)
2. retun efn1/ !options1.lazy && efn1()
3.1 cleanUp(efn1)
3.2 active = efn1
3.3 res = fn1()
4.1 读取p1.k1值/ p1.k1.k1_1值==> get(t1, k1)
5.1 非只读时,track(t1, k1)
5.2 Rres = Reflect.get(t1, k1)==> return Rres/ readonly(Rres)/ reactive(Rres)
4.2 in操作符==> has(t1, k1)
5.1 track(t1, k1)
5.2 return Reflect.has(t1, k1)
4.3 for...in==> ownKeys(t1)
5.1 track(t1, ite_key): 普通对象
5.2 track(t1, 'length'): 数组
3.5 重置active
S2 p1.key1的set
2.1 确定type值:set/ add/ delete
2.2 Rres = Reflect.set(t1, k1)
2.3 trigger(t1, k1, type, newVal1)
3.1 get key's efns
3.2 type为 add/del, 可能会影响for...in结果: get ite's efns
3.3 type为 add + 数组:修改下标可能会影响数组的长度: get length's efns
3.4 k1为length + 数组:修改length,可能会影响arr[0]的读取
四 renderWatcher的执行流程
1 实现 数据观测/数据代理
1 new Vue(options)
2 Vue.pty._init()
3 initData(vm)
4 observe(data, true)
5 new Observer(value)
6.1 value和ob互相关联
6.2 handleObject/ob.walk()==> defineReactive()
6.3 handleArray==> 代理数组原型对象 + ob.observeArray()
6.2 handleObject/ob.walk()
7.defineReactive(obj, keyX)
8.1 keyDep(即setterDep) + getChildOb
8.2 Obj.definePty.get: keyDep.depend() + childObDep?.depend() + dependArray(value) + 返回value值
8.3 observe(newVal) + dep.notify()
6.3 handleArray
7.1 代理数组原型对象 + ob.observeArray(inserted) + ob.dep.notify()
7.2 ob.observeArray(value)
childOb.dep的作用是:
收集无法被setter触发的依赖,从而响应 vm.$set/arr.push等修改方式
也就是说,是 为了在【添加/删除属性、调用数组变异方法】时,能有方法触发 依赖执行
dependArray的作用是:
递归让数组里的 对象/数组成员,也通过ob.dep收集依赖,从而支持vm.$set等方法触发
2 触发依赖收集
1 new Vue(options)
2 Vue.pty._init()
3 initXXX(vm) + vm.$mount(vm.$options.el)
4 getTemplate==> RenderFn(即compileToFunctions函数)
5 mountComponent()
6 new Watcher(vm, updateComponent, noop, {}, true)
6 Watcher.constructor(vm, expOrFn, cb, options, isRenderWatcher)
7.1 建立组件和当前watcher间的联系 + initWatcherGetter
7.2 watcher.get()
8 pushTarget(watcher) + watcher.getter() + popTarget() + watcher.cleanupDeps()
8.1 watcher.getter()==> updateComponent() ==> vm._render()==> $data.xxx的getter拦截 ==> dep1.depend() ==>
9 watcher.addDep(dep1):
- 利用watcher.newDepIds和watcher.depIds 避免重复添加watcher
10 dep1.addSub(watcher)
8.2 watcher.cleanupDeps(): 利用watcher.newDepIds和watcher.depIds,删除过期的依赖(比如之前在页面显示,后来不显示的data_key)==>
dep.removeSub(watcher1)
所以watcher.newDepIds && watcher.depIds的作用,就是用来让dep正确收集watcher的==> 不收集重复的依赖(efn) + 删除过期的依赖(efn)
3 触发依赖
1 修改响应数据的属性值==> setter/ob.dep
2 dep.notify()
3 subItem.update()/ watcherX.update()
4.1 handleCoumputed
4.2 handleSyncWatcher/ watcherX.run()
4.3 queueWatcher(watcherX)
4.3 queueWatcher(watcherX)
5 nextTick(flushSchedulerQueue)==> flushSchedulerQueue()
6 watcherX.run(): watcherX.get() + watcherX.cb()
五 vm.computed流程的执行流程
1 new Vue(options)
2 Vue.pty._init()
3 initState==> initComputed(vm, opts.computed)
4.1 创建compWatcher,并把它添加到 vm._computedWatchers对象里
4.2 调用 defineComputed(vm, key, userDef)
5 定义computedGetter==> compA-Watcher.depend() + compA-Watcher.evaluate()
计算属性响应式流程:模板中使用了计算属性compA==> 编译成渲染函数==> 触发compA的 getter拦截器,即sharedPropertyDefinition.get==> 触发computedGetter==> watcher.depend/ dep.depend/ watcher.addDep/ dep.addSub==> compA.subs = [renderWatcher]==>
调用compA-Watcher.evaluate/watcher.get() 手动求值==> compA内读取了data.key1收集了compA-Watcher
修改$data.key1的值==> 触发compA-Watcher更新,即compA-Watcher.update ==> watcherdep.notify() + compA.subs = [renderWatcher] ==> 页面重新进行渲染,完成视图更新