vue设计与实现 第4章 computed 响应原理 笔记
computed 可借助于effect 实现,只需要扩展其懒加载和缓存功能
effect 代码

let activeEffect; const effectStack = []; function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } effectFn.deps = [] effectFn.options = options effectFn() } const cleanup = (effectFn) => { for (let i = 0; i < effectFn.deps.length; i++) { const deps = effectFn.deps[i] deps.delete(effectFn) } effectFn.deps.length = 0 } const data = { status: true, text: 'asd',num: 0 }; const bucket = new WeakMap(); const obj = new Proxy(data, { get(target, key) { track(target, key) return target[key] }, set(target, key, newVal) { target[key] = newVal trigger(target, key) } }) const track = (target, key) => { 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) } const trigger = (target, key) => { let depsMap = bucket.get(target) if (!depsMap) return let effects = depsMap.get(key) const effectsToRun = new Set() effects.forEach(fn => { if (fn !== activeEffect) { effectsToRun.add(fn) } }) effectsToRun.forEach(fn => { fn.options.scheduler ? fn.options.scheduler(fn) : fn() }) }
之前调用 effect 函数就会立即执行一次,传递过去的副作用函数,而且没返回结果。实现 computed 懒加载功能和返回结果很简单。只需要在 options 添加个状态来管理,和把副作用函数结果直接 return。
扩展代码 懒加载
function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) const res = fn(); // 执行传递过来的副作用函数,保存结果 effectStack.pop() activeEffect = effectStack[effectStack.length - 1] return res // 返回结果 } effectFn.deps = [] effectFn.options = options // 只有 lazy 为 false 执行,返回包装过的副作用函数 if(!options.lazy) { effectFn() } return effectFn } effect(()=>{ console.log(obj.num); },{ lazy: true })
扩展完我们可以开始 封装 computed 函数
function computed(getter) { // 把传递给 computed 的 getter 函数 传递给 effect 作为副作用函数 const effectFn = effect(getter,{ lazy: true }) // computed 返回一个 对象,获取 value 属性的时候 调用 effect 副作用函数,并返回其返回值 const obj = { get value(){ return effectFn() } } return obj } const increment = computed(()=>obj.num+1) increment.value // 1
大概意思就是在 computed 函数里边调用 effect ,创建一个对象,并把副作用函数当作这个对象的 value 属性的 get 函数,返回这个对象。所以只要调用 computed 返回值的 value 属性便能够手动
触发副作用函数。但是,现在还没有缓存计算结果,每次调用 computed 都是重新执行一遍副作用函数。
继续扩展 缓存
function computed(getter) { let value; // 缓存数据 let flush = true // 添加状态控制缓存数据 const effectFn = effect(getter,{ lazy: true, // 响应式数据发生变化的时候, 发现有 scheduler 会调用 scheduler scheduler(){ flush = true // 响应式数据改变,开放状态, } }) const obj = { get value(){ if(flush){ // true 表示数据改变需要重新获取数据 value = effectFn() flush = false // 获取数据完毕,关闭状态 } return value } } return obj } const increment = computed(()=>obj.num+1) console.log(increment.value );// 1 console.log(increment.value );// 1 obj.num++ console.log(increment.value );// 2
实现也是非常简单,添加 flush 变量控制。初始值和响应式数据变化 赋值为 true,调用副作用函数后赋值为 false。
现在的 computed 还有一个缺陷,在 effect 中读取计算属性值时, 数据改变没触发副作用函数。例子
const increment = computed(()=>obj.num+1) effect(()=>{ console.log(increment.value); }) obj.num++ // effect 没触发
解决方法:获取计算属性的时候手动触发 track 收集 obj的value属性的副作用函数, computed 依赖的数据改变的时候会触发 scheduler 函数,这时候再手动触发用到 obj的value 属性的副作用函数,也就是 使用了计算属性的副作用函数
function computed(getter) { let value; let flush = true const effectFn = effect(getter,{ lazy: true, scheduler(){ flush = true trigger(obj,'value') // 触发 obj value 属性的 effect 函数 } }) const obj = { get value(){ if(flush){ value = effectFn() flush = false } track(obj,'value') // 收集 使用了 obj value 属性的 effect 函数 return value } } return obj }
分类:
vue 设计与实现
标签:
vue.js
, vue.js 设计与实现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)