0%

代理object

对一个普通对象所有可能的读取操作:

  • 访问属性: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
//取得与key相关联的副作用函数
const effects=depsMap.get(key)
//取得与ITERATE_KEY相关联的副作用函数
const iterateEffects = depMaps.get(ITER)
const effectsToRun=new Set(effects)
//当操作类型是ADD或者DELETE,需要触发与ITERATE_KEY相关的副作用函数执行
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){//只有当trigger触发执行的副作用函数和当前正在执行的副作用函数不相同时才触发执行,否则会出现栈溢出
effectsToRun.add(effectfn)
}
})
effectsToRun.forEach(effectfn=>effectfn())
//effect&&effect.forEach(fn=>fn())//会产生无限执行
}

如何屏蔽由原型引起的更新:

1
2
3
4
5
6
7
8
9
10
const obj={}
const proto={bar:1}
const child=reactive(obj)
const parent=reactive(proto)
//使用parent作为child的原型
Object.setPrototypeOf(child,parent)
effect(()=>{
console.log(child.bar)//1
})
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){
//通过"raw”属性访问原始对象
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)
//说明receiver是target的代理对象
if(target===receiver.raw){
//比较新值和旧值,只有当它们不全等并且都不是NAN才触发响应
if(oldValue!==newValue&&(oldValue===oldValue||newValue===newValue)){
trigger(target,key)
}
}

return true;
}
})
}