Vue.js 设计与实现
2023-01-15 10:54:18 0 举报
AI智能生成
Vue.js 设计与实现思维导图
作者其他创作
大纲/内容
响应系统的作用与实现
(如何实现响应系统)
(如何实现响应系统)
实现响应式系统
背景描述
背景: 有obj是个对象,有effectFn是个函数,effect函数里面有读取obj.xxx的操作
目的: 希望obj.xxx修改后, effectFn能够自动执行
-------------------------
obj - 响应式数据
effectFn - 副作用函数
effect - 副作用注册函数(相当于render/h函数)
目的: 希望obj.xxx修改后, effectFn能够自动执行
-------------------------
obj - 响应式数据
effectFn - 副作用函数
effect - 副作用注册函数(相当于render/h函数)
基本实现
1. 利用Object.definedProperty/Proxy劫持数据的get/set
2. 在副作用函数执行时读取数据时触发拦截get, 从而将当前副作用函数加入到bucket中
3. 在修改数据的属性值时触发拦截set, 批量执行存在bucket中的副作用函数
完善系统
问题1: 满足不同obj/key
存在不同属性/和不同obj的副作用函数,
bucket的set数据格式满足不了层级需求
存在不同属性/和不同obj的副作用函数,
bucket的set数据格式满足不了层级需求
将bucket的数据结构从单层set数据结构改为三层结构
1. 第一层使用weakMap obj为key
2. 第二层使用map obj.xxx为key
3. 第三层使用set将副作用函数存入
1. 第一层使用weakMap obj为key
2. 第二层使用map obj.xxx为key
3. 第三层使用set将副作用函数存入
为什么使用weakMap?
key值因为是obj,weakMap弱引用obj, 不阻碍垃圾回收机制
为什么使用map?
key值只是字符串
为什么使用set?
去重
因为存储数据结构变化
所以在收集/读取时候也要调整
所以在收集/读取时候也要调整
get函数, 收集副作用函数时会
1. bucket将读取bucket[obj], 无则创建
2. 继续读取bucket[obj][key], 无则创建
3. 最后存入副作用函数
结构: { [obj]: { [key]: [effectFn1, effectFn2] } }
1. bucket将读取bucket[obj], 无则创建
2. 继续读取bucket[obj][key], 无则创建
3. 最后存入副作用函数
结构: { [obj]: { [key]: [effectFn1, effectFn2] } }
set函数, 通过set的第一二个参数获取bucket[obj][key], 最后批量执行effectFnArr
问题2: effectFn硬编码问题
get收集副作用函数,这里收集的副作用函数(effectFn)是写死的
(因为收集时并不知道哪个函数触发了get, 所以demo硬编码函数名)
get收集副作用函数,这里收集的副作用函数(effectFn)是写死的
(因为收集时并不知道哪个函数触发了get, 所以demo硬编码函数名)
添加副作用注册函数 effect,接收副作用函数
它的功能有:
1. 执行时将当前副作用函数赋给全局变量activeEffect
2. 执行effectFn(此时会触发obj get的拦截)
它的功能有:
1. 执行时将当前副作用函数赋给全局变量activeEffect
2. 执行effectFn(此时会触发obj get的拦截)
修改拦截get函数
1. 存入全局变量activeEffect
1. 存入全局变量activeEffect
分支的处理
问题1: 切换分支时,effectFn不依赖obj[key], 但是修改obj[key], 还是触发effectFn
通过分支代码改变了副作用的依赖,使其没有依赖obj.xxx,
但是因为第一次收集时将obj.xxx的副作用收集了,所以修改obj.xxx还是触发effectFn
通过分支代码改变了副作用的依赖,使其没有依赖obj.xxx,
但是因为第一次收集时将obj.xxx的副作用收集了,所以修改obj.xxx还是触发effectFn
解决思路:
在副作用函数身上增加deps数组记录依赖的bucket的obj.key list
每次拦截get时将bucket obj.key list存入deps
在每次触发副作用函数时,将存储的deps遍历删除副作用函数
在副作用函数身上增加deps数组记录依赖的bucket的obj.key list
每次拦截get时将bucket obj.key list存入deps
在每次触发副作用函数时,将存储的deps遍历删除副作用函数
因为执行副作用函数需要前置清除依赖,
所以将副作用函数套一层,
1. activeEffect指向套的这一层
2. 套函数增加属性deps = []
3. 套函数执行副作用函数前清除依赖
所以将副作用函数套一层,
1. activeEffect指向套的这一层
2. 套函数增加属性deps = []
3. 套函数执行副作用函数前清除依赖
触发get收集依赖
1. 在读取的map层,将map推入activeEffect.deps
1. 在读取的map层,将map推入activeEffect.deps
细节:
在拦截set触发副作用函数时,因为是Set forEach遍历,
如果以前被访问过的值,删除重新推入,会继续访问,
所以这里会无限循环
解决方法: Set数据重新用个新Set存储再forEach执行副作用函数
产生无限循环的流程:
1. 修改obj.key
2. 触发set
3. tigger收集的副作用函数(Set forEach执行副作用函数)
4. 副作用函数执行
5. 清除依赖
6. 触发get
7. 又把副作用函数加入Set中了
8. 此时Set forEach还在执行
9. 访问过的值还会继续访问
10. 无限循环
在拦截set触发副作用函数时,因为是Set forEach遍历,
如果以前被访问过的值,删除重新推入,会继续访问,
所以这里会无限循环
解决方法: Set数据重新用个新Set存储再forEach执行副作用函数
产生无限循环的流程:
1. 修改obj.key
2. 触发set
3. tigger收集的副作用函数(Set forEach执行副作用函数)
4. 副作用函数执行
5. 清除依赖
6. 触发get
7. 又把副作用函数加入Set中了
8. 此时Set forEach还在执行
9. 访问过的值还会继续访问
10. 无限循环
处理effect嵌套
背景:
在例如vue中,effect一般有组件的render方法,
组件肯定是会嵌套使用的,所以effect也会出现嵌套的情况
在例如vue中,effect一般有组件的render方法,
组件肯定是会嵌套使用的,所以effect也会出现嵌套的情况
问题1:
在前面写的例子中,使用activeEffect指定当前的副作用函数,
当发生effect嵌套时会导致activeEffect指向不对,导致在get时存储的副作用函数不正确
在前面写的例子中,使用activeEffect指定当前的副作用函数,
当发生effect嵌套时会导致activeEffect指向不对,导致在get时存储的副作用函数不正确
解决思路:
使用一个栈结构存储嵌套的每一个副作用函数,在执行get前将当前副作用推入,get执行后出栈,
并将activeEffect指向栈顶元素,保证了每次activeEffect正确指向问题
使用一个栈结构存储嵌套的每一个副作用函数,在执行get前将当前副作用推入,get执行后出栈,
并将activeEffect指向栈顶元素,保证了每次activeEffect正确指向问题
1. 增加effectStack = []
2. effect中effectFn执行时,将当前effectFn推入effectStack中,并且activeEffect指向effectFn
3. 执行fn(这里fn嵌套了effect)
4. 执行嵌套的effect, 将当前effectFn推入effectStack中,并且activeEffect指向effectFn
5. 执行嵌套fn
6. 触发get收集完,嵌套fn执行完,将栈顶元素出栈
7. 将activeEffect指向栈顶元素
8. 继续执行fn
9. 触发get收集完
2. effect中effectFn执行时,将当前effectFn推入effectStack中,并且activeEffect指向effectFn
3. 执行fn(这里fn嵌套了effect)
4. 执行嵌套的effect, 将当前effectFn推入effectStack中,并且activeEffect指向effectFn
5. 执行嵌套fn
6. 触发get收集完,嵌套fn执行完,将栈顶元素出栈
7. 将activeEffect指向栈顶元素
8. 继续执行fn
9. 触发get收集完
处理无限循环
问题1:
当注册副作用函数的函数中出现
同一个属性又读又写的情况会出现无限循环
例如 obj.foo++
当注册副作用函数的函数中出现
同一个属性又读又写的情况会出现无限循环
例如 obj.foo++
解决思路:
通过观察上面无限循环的流程,发现触发get的收集effectFn和set执行的effectFn是同一个函数,
所以在set触发执行effectFn时判断一下是否与get时的函数是否是同一个就行
通过观察上面无限循环的流程,发现触发get的收集effectFn和set执行的effectFn是同一个函数,
所以在set触发执行effectFn时判断一下是否与get时的函数是否是同一个就行
1. trigger函数中forEach执行effectFn时判断与当前的activeEffect是否相同
如果相同则不执行
如果相同则不执行
实现调度执行
背景:
我们修改一个属性值多次,其实我们只关注它最后一次的结果,而不是中间过程
(例如React中, setState一个属性值多次,只会打印一次结果)
我们修改一个属性值多次,其实我们只关注它最后一次的结果,而不是中间过程
(例如React中, setState一个属性值多次,只会打印一次结果)
如何实现调度执行?
解决思路:
梳理系统流程我们可知set后才触发副作用函数的执行,
所以在副作用执行的时候我们可以用scheduler去执行副作用函数,
通过set任务队列,始终保持最后一个副作用函数在队列中(相当于防抖,一个事件循环只执行最后一次)
梳理系统流程我们可知set后才触发副作用函数的执行,
所以在副作用执行的时候我们可以用scheduler去执行副作用函数,
通过set任务队列,始终保持最后一个副作用函数在队列中(相当于防抖,一个事件循环只执行最后一次)
实现scheduler:
1. 在effect注册副作用时可以增加一个options参数
2. 在set拦截触发tigger时判断当前副作用身上的options是否有scheduler,
如果有的话将副作用函数交由scheduler去执行
3. 在effect注册函数第一次执行时,将options放到副作用函数身上,方便在tigger是读取options
1. 在effect注册副作用时可以增加一个options参数
2. 在set拦截触发tigger时判断当前副作用身上的options是否有scheduler,
如果有的话将副作用函数交由scheduler去执行
3. 在effect注册函数第一次执行时,将options放到副作用函数身上,方便在tigger是读取options
实现任务队列:
1. 创建任务队列
2. 创建执行任务队列函数
3. scheduler调度时,将副作用函数加入任务队列,执行任务队列函数
(因为任务队列是set,所以相同的副作用函数只会只有一个)
4. 任务队列函数设置flag阻止第二次进入执行,并开启一个异步任务批量执行
任务队列中的副作用,待异步任务完成,关闭flag,等待下一次执行任务队列函数
1. 创建任务队列
2. 创建执行任务队列函数
3. scheduler调度时,将副作用函数加入任务队列,执行任务队列函数
(因为任务队列是set,所以相同的副作用函数只会只有一个)
4. 任务队列函数设置flag阻止第二次进入执行,并开启一个异步任务批量执行
任务队列中的副作用,待异步任务完成,关闭flag,等待下一次执行任务队列函数
0 条评论
下一页