effect 是可以发生嵌套的
effect(function effectFn1(){
effect(function effectFn2(){
/* .... */
})
/* .... */
})
如下代码嵌套执行
// 全局变量
let temp1, temp2
// effectFn1 嵌套了 effectFn2
effect(function effectFn1(){
console.log('effectFn1 执行')
effect(function effect2(){
console.log('effectFn2 执行')
// 在 effectFn2 中读取 obj.bar 属性
temp2 = obj.bar
})
// 在 effectFn1 中读取 obj.foo 属性
temp1 = obj.foo
})
问题:希望当修改obj.foo时会触发effectFn1执行。由于effectFn2嵌套在effectFn1里,所以会间接触发effectFn2执行,而当修改obj.bar时,只会触发俄effectFn2。但结果 不是这样,当修改obj.foo时,输出为:
'effectFn1 执行'
'effectFn2 执行'
'effectFn2 执行'
原因:发生嵌套时activeEffect会存储内层副作用函数,不会恢复到原来的值。
解决办法:创建一个effectStack栈,副作用函数执行时,将当前副作用函数压入栈中,待副作用函数执行完将其从栈中弹出,并始终让activeEffect指向栈顶的副作用函数:
// 用一个全局变量储存被注册的副作用函数
let activeEffect
// effect 栈
const effectStack = []
// effect 函数用于注册副作用函数
function effect(fn){
// 当 effectFn 执行时,将其设置为当前激活的副作用函数
const effectFn = ()=>{
// 调用cleanup完成清除工作
cleanup(effectFn)
// 当调用effect注册副作用函数时,将副作用函数fn赋值给activeEffect
activeEffect = effectFn
// 在调用副作用函数前将其压入栈中
effectStack.push(effectFn)
// 执行副作用函数
fn()
// 在当前副作用函数执行完后,将其弹出,并把activeEffect还原为之前的值
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
// activeEffect.deps 用来存储所有与被副作用函数相关的依赖集合
effectFn.deps = []
// 执行副作用函数
effectFn()
}
// 清除工作
function cleanup(effectFn){
// 遍历`effectFn.deps 数组
for(let i=0; i< effectFn.deps.length; i++){
// deps 是依赖集合
const deps = effectFn.deps[i]
// 将 effectFn 从依赖集合中移除
deps.delete(effectFn)
}
// 最后需要重置 effectFn.deeps 数组
effectFn.deps.length = 0
}
// 储存副作用函数的桶
const bucket = new WeakMap()
// 原始数据
const data = { text: 'hello world',ok:true, foo:true, bar:true,three:true}
// 对原始数据的代理
const obj = new Proxy(data,{
// 拦截读取操作
get(target,key){
track(target,key)
// 返回属性值
return target[key]
},
// 拦截设置操作
set(target,key,newValue){
console.log('bucket:',bucket)
// 设置属性值
target[key] = newValue
trigger(target,key)
return true
}
})
// 在 get 拦截函数内调用 track 函数追踪变化
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 关联
if(!deps){
depsMap.set(key,(deps = new Set()))
}
// 最后将当前激活的副作用函数添加到“桶”
deps.add(activeEffect)
// deps 就是一个与当前副作用函数存在关联的依赖集合
// 将其添加到activeEffect.deps 数组中
activeEffect.deps.push(deps) // 新增
}
// 在 set 拦截函数内调用 trigger 函数 触发变化
function trigger(target,key){
// 根据 target 从桶中取得 depsMap,它是key --》effects
const depsMap = bucket.get(target)
if(!depsMap) return true
// 根据 key 取得所有副作用函数 effects
const effects = depsMap.get(key)
// new Set 是为了防止 effectFn 执行时,进行无限的循环
const effectsToRun = new Set()
effects && effects.forEach(effectFn => {
// 如果 trigger 触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
if(effectFn !== activeEffect){
effectsToRun.add(effectFn)
}
})
// 执行副作用函数
effectsToRun && effectsToRun.forEach(fn => fn())
}
// 全局变量
let temp1, temp2, temp3
// effectFn1 嵌套了 effectFn2
effect(function effectFn1(){
console.log('effectFn1 执行')
effect(function effect2(){
console.log('effectFn2 执行')
// 在 effectFn2 中读取 obj.bar 属性
temp2 = obj.bar
effect(function effect3(){
console.log('effectFn3 执行')
// 在 effectFn3 中 读取 obj.three 属性
temp3 = obj.three
})
})
// 在 effectFn1 中读取 obj.foo 属性
temp1 = obj.foo
})
setTimeout(()=>{
obj.bar = false
},2000)