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)