响应系统的设计与实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | // 存储副作用函数的桶 const bucket = new WeakMap() // 原始数据 export const listenInit = (data) => { return new Proxy(data, { // 拦截读取操作 get(target, key) { track(target, key) // 返回属性值 return target[key] }, // 拦截设置操作 set(target, key, newVal) { // 设置属性值 target[key] = newVal trigger(target, key) return Reflect.set(...arguments) }, }) } // 在 get 拦截函数内调用 track 函数追踪变化 function track(target, key) { // 没有 activeEffect,直接 return if (!activeEffect) return let depsMap = bucket.get(target) if (!depsMap) { bucket.set(target, (depsMap = new Map())) } let deps = depsMap.get(key) if (!deps) { depsMap.set(key, (deps = new Set())) } deps.add(activeEffect) activeEffect.deps.push(deps) } // 在 set 拦截函数内调用 trigger 函数触发变化 function trigger(target, key) { const depsMap = bucket.get(target) if (!depsMap) return const effects = depsMap.get(key) const effectsToRun = new Set() effects && effects.forEach((effectFn) => { if (effectFn !== activeEffect) { effectsToRun.add(effectFn) } }) effectsToRun.forEach((effectFn) => { // 如果一个副作用函数存在调度器,调用调度器,然后将副作用函数作为参数传递 if (effectFn.options.scheduler) { effectFn.options.scheduler(effectFn) } else { //否则直接执行副作用函数 effectFn() } }) } // 用一个全局变量存储被注册的副作用函数 let activeEffect let effectStack = [] // effect 函数用于注册副作用函数 export const jobEffect = (fn, options = {}) => { const effectFn = () => { cleanup(effectFn) // 当 effectFn 执行时,将其设置为当前激活的副作用函数 activeEffect = effectFn effectStack.push(effectFn) const res = fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] return res } // 将options挂载到effectFn上 effectFn.options = options // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合 effectFn.deps = [] // 执行副作用函数 if (!options.lazy) { effectFn() } return effectFn } function cleanup(effectFn) { // 遍历 effectFn.deps 数组 for ( let i = 0; i < effectFn.deps.length; i++) { // deps 是依赖集合 const deps = effectFn.deps[i] // 将 effectFn 从依赖集合中移除 deps. delete (effectFn) } // 最后需要重置 effectFn.deps 数组 effectFn.deps.length = 0 } // 定义一个任务队列 const jobQueue = new Set() // 使用 Promise.resolve() 创建一个 promise 实例,我们用它将一个任务添加到微任务队列 const p = Promise.resolve() // 一个标志代表是否正在刷新队列 let isFlushing = false function flushJob() { console.log( 'flushing!!' ) // 如果队列正在刷新,则什么都不做 if (isFlushing) return console.log( 'flusingDone!!!' ) // 设置为 true,代表正在刷新 isFlushing = true // 在微任务队列中刷新 jobQueue 队列 p.then(() => { jobQueue.forEach((job) => job()) }).finally(() => { // 结束后重置 isFlushing isFlushing = false }) } export const scheduler = (fn) => { // 每次调度时,将副作用函数添加到 jobQueue 队列中 jobQueue.add(fn) // 调用 flushJob 刷新队列 flushJob() } export const jobWatch = (source, cb, options = {}) => { let getter if ( typeof source === 'function' ) { getter = source } else { getter = () => traverse(source) } // 定义旧值与新值 let oldValue, newValue // 提取 scheduler 调度函数为一个独立的 job 函数 const job = () => { newValue = effectFn() cb(newValue, oldValue) oldValue = newValue } // 使用 effect 注册副作用函数时,开启 lazy 选项,并把返回值存储到 effectFn 中以便后续手动调用 const effectFn = jobEffect(() => getter(), { lazy: true , scheduler: job, }) if (options.immediate) { // 当 immediate 为 true 时立即执行 job,从而触发回调执行 job() } else { oldValue = effectFn() } } function traverse(value, seen = new Set()) { // 如果要读取的数据是原始值,或者已经被读取过了,那么什么都不做 if ( typeof value !== 'object' || value === null || seen.has(value)) return // 将数据添加到 seen 中,代表遍历地读取过了,避免循环引用引起的死循环 seen.add(value) // 暂时不考虑数组等其他结构 // 假设 value 就是一个对象,使用 for...in 读取对象的每一个值,并递归地调用 traverse 进行处理 for (const k in value) { traverse(value[k], seen) } return value } // 调用方法 import { listenInit, jobWatch } from './jobQueue' let errorMessage = listenInit({ msg: '' }) jobWatch( () => errorMessage.msg, (newVal, oldVal) => { if (newVal != oldVal) { message.error(newVal) } } ) |
《Vue.js设计与实现》,书写的真好,学习到很多,vue3实现数据响应的方式了解了。
“他的眼睛很小,跟猪的差不多,却闪动着十足的热情。不知是怎的,人们见到这样的人总是心生厌恶” ——《巴黎伦敦落魄记》
这么回事嘛,要内敛一些呀。
分类:
JavaScript
, 读书随笔
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」