计算属性最终执行时的代码,要达到3个目的
- obj.foo 或 obj.bar 任何一个值被重新赋值时,自动计算 sumRes 的值
- sumRes 的值发生变化后 function abc () { console.log(sumRes.value) } 同时执行
- obj.foo 或 obj.bar 的变化同时会导致 sumRes 的变化,所以也要运行副作用函数 abc
const sumRes = computed(() => obj.foo + obj.bar)
effect(function abc () {
console.log(sumRes.value)
})
obj.foo = 10
我认为的关键点
- 计算属性 obj.value 的 getter 里面的 track 对 obj 的 value 属性相关的副作用函数进行了依赖收集。
- 计算属性注册副作用函数时的 scheduler 调度器方法很妙,没有把副作用函数作为参数传递并执行。这样就在sumRes 相关的 obj.foo 或 obj.bar 值改变时会直接运行相关依赖副作用函数的 scheduler。计算属性内部的 dirty = true,代表下次读取 sumRes 时不读缓存重新计算;然后 scheduler 的 trigger 调用直接触发计算属性相关的依赖函数执行,那么包裹 abc 函数的副作用函数将被执行,执行时读取 sumRes.value,从而计算属性的 value 的 getter 将被执行,最后重新计算 value,收集依赖,返回更新后的值。
附上这章最终最终的代码
const bucket = new WeakMap()
let activeEffect;
const effectStack = []
const 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.options = options
effectFn.deps = []
if (!options.lazy) {
effectFn()
}
return effectFn
}
function cleanup (effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length = 0
}
function computed (getter) {
let value
let dirty = true
const effectFn = effect(getter, {
lazy: true,
scheduler () {
if (!dirty) {
dirty = true
trigger(obj, 'value')
}
}
})
const obj = {
get value () {
if (dirty) {
value = effectFn()
dirty = false
}
track(obj, 'value')
return value
}
}
return obj
}
const data = { foo: 1, bar: 2 }
const obj = new Proxy(data, {
get (target, key) {
track(target, key)
return target[key]
},
set (target, key, newVal) {
target[key] = newVal
trigger(target, key)
}
})
function 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)
}
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()
}
})
}
const jobQueue = new Set()
const p = Promise.resolve()
let isFlushing = false
function flushJob () {
if (isFlushing) return
isFlushing = true
p.then(() => {
jobQueue.forEach(job => job())
}).finally(() => {
isFlushing = false
})
}
const sumRes = computed(() => obj.foo + obj.bar)
effect(function abc () {
console.log(sumRes.value)
})
obj.foo = 10
附上关键步骤的执行流程图

github 地址
gitee 地址