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