let activeEffect
const effectStack = []
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.options = options
effectFn.deps = []
if (!options.lazy) {
effectFn()
}
return effectFn
}
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 effectToRun = new Set()
effects && effects.forEach(effectFn => {
if (effectFn !== activeEffect) {
effectToRun.add(effectFn)
}
})
effectToRun.forEach(effectFn => {
if (effectFn.options.scheduler) {
effectFn.options.scheduler(effectFn)
} else {
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
}
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 data = { foo: 1, bar: 2 }
const bucket = new WeakMap()
const obj = new Proxy(data, {
get(target, key, receiver) {
track(target, key)
return Reflect.get(target, key, receiver)
},
set(target, key, newVal, receiver) {
const res = Reflect.set(target, key, newVal, receiver)
trigger(target, key)
return res
}
})
const effectFn = effect(() => obj.foo + obj.bar, {
lazy: true
})
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
}
function watch(source, cb) {
effect(() => source.foo),
{
scheduler() {
cb()
}
}
}
const sumRes = computed(() => obj.foo + obj.bar)
effect(() => {
console.log(sumRes.value)
})
effect(() => {
console.log('副作用触发')
obj.foo
})
obj.foo++
console.log(sumRes.value)