对一个普通对象所有可能的读取操作:
- 访问属性:obj.foo
- 判断对象原型上是否存在给定属性key in obj
- 使用for…in循环遍历对象:for(const key in obj){}
1、对于属性的读取直接用get拦截函数
2、对于in 操作符,用has拦截函数代理
1 2 3 4 5 6 7
| const obj={foo:1} const p= new Proxy(obj,{ has(target,key){ track(target,key) return Reflect.has(target,key) } })
|
3、对于for…in的拦截,使用Reflect.ownKeys()
在使用track函数进行追踪时,将ITERATE_KEY作为追踪的key,这是因为ownKeys拦截函数,只能拿到目标对象target,用ownKeys来获取一个对象中的所有属于自己的键值时,这个操作明显不与任何键绑定,因此我们只能够构造唯一的key作为标识(用Symbol构造),即ITERATE_KEY
export const ITERATE_KEY = Symbol(DEV ? ‘iterate’ : ‘’)
修改属性不会对for循环产生影响,因为无论怎么修改一个值,对于for…in循环来说都只会循环一次,如果是添加属性或者删除属性,就会触发副作用函数重新执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const ITERATE_KEY=Symbol() function trigger(target,key){ const depsMap=bucket.get(target) if(!depsMap)return const effects=depsMap.get(key) const iterateEffects = depMaps.get(ITER) const effectsToRun=new Set(effects) if(type==='ADD'||type==='DELETE'){ const iterateEffects=depsMap.get(ITERATE_KEY) iterateEffects&&iterateEffects.forEach(effectfn=>{ if(effectfn!==activeEffect){ effectsToRun.add(effectfn) } }) } effects&&effects.forEach(effectfn=>{ if(activeEffect!=effectfn){ effectsToRun.add(effectfn) } }) effectsToRun.forEach(effectfn=>effectfn()) }
|
如何屏蔽由原型引起的更新:
1 2 3 4 5 6 7 8 9 10
| const obj={} const proto={bar:1} const child=reactive(obj) const parent=reactive(proto)
Object.setPrototypeOf(child,parent) effect(()=>{ console.log(child.bar) }) child.bar=2
|
根据规范10.1.9.2:
如果ownDesc是undefined,那么:
a.让parent的值为O.[[GetPrototypeOf]] ()
b.如果parent不是null,则
返回?parent.[[Set]] (P,V,Receiver)
c.将ownDesc设置为{[[Value]]:undefined,[[Writable]]:true,[[Enumerable]]:true,[[Configurable]]:true}
如果设置的属性不在对象上,那么就会取得原型,并调用其原型上的方法,也就是parent[[Set]]内部方法,由于parent是代理对象,所以相当于执行了它的set拦截函数,因此读取child.bar时,副作用函数被child.bar收集,还被parent.bar收集
需要屏蔽一次,而parent代理对象的set函数执行时,此时target是原始对象proto,receiver是代理对象child,不再是target的代理对象,由于最初设置child.bar,receiver一直都是child,target是变化的
只有当receiver是target的代理对象时才触发更新,就能屏蔽由原型引起的更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function reactive(obj){ return new Proxy(obj,{ get(target,key,receiver){ if(key==='raw'){ return target } track(target,key) return Reflect.get(target,key,receiver) }, set(target,key,newValue){ const oldValue=target[key] const type=Object.prototype.hasOwnProperty.call(target,key)?'SET':'ADD' const res=Reflect.set(target,key,receiver,newValue) if(target===receiver.raw){ if(oldValue!==newValue&&(oldValue===oldValue||newValue===newValue)){ trigger(target,key) } } return true; } }) }
|