const data = { foo: 1 }
const obj = new Proxy(data, {/*...*/})
effect(() => obj.foo = obj.foo + 1)
此项操作会引起栈溢出:
Uncaught RangeError: Maximum call sack size exceeded
在此操作中, 会先读取obj.foo
的值, 这会触发track
操作, 将副作用函数入栈, 此时有加一并赋值, 此时会触发trigger
操作, 将副作用函数出栈并执行, 在这种情况下, 该副作用函数还在执行中, 又开始下一次的执行, 导致无限递归调用自己导致栈溢出报错.
在这个操作中读取与设置的是同一个副作用函数activeEffect
, 因此在trigger
要触发时添加条件: 如果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 => effectFn())
}
目前为止响应式完整代码
// 储存副作用函数的桶
const bucket = new WeakMap()
// 用于储存被注册的副作用的函数
let activeEffect = undefined
// 副作用函数栈
const effectStack = []
function cleanup (effectFn) {
for (let itme of effectFn.deps) {
itme.delete(effectFn)
}
effectFn.deps.length = []
}
function effect (fn) {
const effectFn = () => {
cleanup(effectFn)
// 调用当前的副作用函数时, 赋值给 全局变量
activeEffect = effectFn
// 在调用副作用函数之前将该函数压入栈
effectStack.push(effectFn)
fn()
// 当前的副作用函数执行结束后, 出栈
effectStrack.pop()
// activeEffect 还原为之前的值
activeEffect = effectStack[effectStack.length - 1]
}
effectFn.desp = []
effectFn()
}
const data = {
text: 'hello world',
ok: true
}
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) {
// 没有 activeEffect, 直接 return
if (!activeEffect) return target[key]
// 根据 target 从'桶'中回去 depsMap, 它也是一个 Map 类型: key ---> effects
let depsMap = bucket.get(target)
// 如果 depsMap 不存在, 则新建一个 Map 并与 target 关联
if (!depsMap) bucket.set(target, (depsMap = new Map()))
// 再根据 key 从depsMap 中去的 deps, 它是一个 Set 类型
// 里面存贮所有与当前 key 相关的副作用函数: effects
let deps = depsMap.get(key)
// 如果 deps 不存在, 同样新建一个 Set 并与 key 关联0
if (!deps) depsMap.set(key, (deps = new Set()))
// 最后将当前激活的副作用函数添加到'桶'里
deps.add(activeEffect)
}
function trigger (target, key) {
// 根据 target 从'桶'中取得 depsMap, 它是 key --> effects
const depsMap = bucket.get(target)
if (!depsMap) return
// 根据 key 取得所有的副作用函数 effects
const effects = depsMap.get(key)
// 执行副作用函数
const effectsToRun = new Set()
effects && effects.forEach(effectFn => {
// 若触发执行的副作用函数与当前正在执行的副作用函数相同, 则不触发执行
if (effectFn !== activeEffect) {
effectsToRun.add(effectFn)
}
})
effectsToRun.forEach(effectFn => effectFn())
}
effect(() => {
console.log('effect run');
document.body.innerText = obj.ok ? obj.text : 'not'
})
setTimeout(() => {
obj.ok = false
}, 2000)