-
in 操作, 对应 proxy 是 has 方法,
const obj = { foo: 1 } const p = new Proxy(obj, { has(target, key) { // has 方法进行track track(target, key) return Reflect.has(target, key) }, }) effect(() => { foo in obj // 通过has方法建立依赖关系 }) -
对象 for in 操作,for in 的内部方法对应 proxy 来说就是 ownKeys, 但是对 for in 进行 track 时,没有具体的 key,需要手动指定一个 key
-
新增一个属性 for in 会变化,操作类型是 ADD 需要在 proxy set 方法里面判断这个属性是新增还是修改
-
删除一个属性 for in 会变化,操作类似是 DELETE,需要通过 proxy deleteProperty 进行特别处理
相当于 set deleteProperty 都需要处理两个依赖,而不是专属于 key 的依赖
let ITERATE_KEY = symbol() const proxy = new Proxy( { foo: 1 }, { set(target,key,newVal,receiver) { const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD' let res = Reflect.set(target,key,newVal,receiver) trigger(target,key,type) return res } ownKeys(target) { // ownKeys 没有具体的key,需要在track时 手动 key 为 ITERATE_KEY track(target, ITERATE_KEY) return Reflect.ownKeys(target) }, deleteProperty(target,key) { const res = Reflect.deleteProperty(target,key) const hadKey = Object.prototype.hasOwnProperty.call(target,key) if (res && hasKey) { // 只有当删除一个已存在的属性 && 删除成功时,才需要trigger trigger(target, key, 'DELETE') } } } ) effect(()=>{ for (let key in proxy) {} }) proxy.count = 1 // effect 需要重新执行, 但是trigger拿到的key是count,只会触发count收集到的依赖,所以在添加属性时,要将ITERATE_KEY收集到的副作用函数重新执行 function trigger(target,key,type) { const depsMap = bucket.get(target) const effectsToRun = new Set() /** * 操作类似是ADD时, trigger 专门获取 target ITERATE_KEY收集的依赖,然后触发 * 操作类型是 DELETE时,也需要特别处理 */ if (type === 'ADD' || type === 'DELETE') { const iterateEffects = depsMap.get(ITERATE_KEY) iterateEffects && iterateEffects.forEach(effect => { if (effect !== activeEffect) { effectsToRun.add(effect) } }) } } -
-
修改原型链上的属性,导致副作用函数触发多次
const child = reactive({}) const parent = reactive({ count: 1 }) Object.setPropertyOf(child, parent) // 继承 parent effect(() => { /** * child.count 收集了副作用函数 * parent.count 也收集了副作用函数 * 根据原型链的查找规则,访问了child.count 收集一次依赖,child没有count属性,沿着原型链查找 到parent,parent也是响应式的,有count属性,所以也会访问 parent.count 属性 */ console.log(child.count) }) child.count = 2 // 副作用函数被执行了两次,同上面的道理 child.set & parent.set 都触发了 // 是 set 导致 trigger触发了两次,需要在 set 里面屏蔽一次不必要的trigger就可以了 get(target,key) { /** * 响应式对象通过 raw 获取原始对象 */ if (key === 'raw') { return target } } set(target,key,newVal,receiver) { /** * chiil.count -> reciver = child * 隐式的 parent.count -> receiver = child * 两次receiver 都是 child * 只有代理对象的原始对象 = target时 才触发trigger */ if (receiver.raw === target) { trigger(target,key) } }