0%

理解vue3中Proxy和Reflect

理解Proxy:

1、使用Proxy可以代理一个对象,它能够实现对其他对象的代理,代理指的是对一个对象的基本语义的代理

基本语义:可以对一个对象进行读取,设置属性值的操作。

2、根据ECMAScript规范,在JavaScript中有两种对象,一种是常规对象,一种是异质对象,这两种对象包含了JavaScript世界中所有对象。

在JavaScript中,对象的实际语义是由对象的内部方法指定的,内部方法指的是对一个对象进行操作时在引擎内部调用的方法,这些方法对于js使用者来说是不可见的,比如[[Get]],[[Set]],[[GetPrototyeOf]],[[SetPrototypeOf]]等方法,

3、区分普通对象和函数:函数会部署内部方法[[Call]],而普通对象不会

4、常规对象需满足:

对于内部[[Get]],[Set],必须使用ECMA规范10.1.x节给出的定义实现

对于内部方法[[Call]],必须使用ECMA规范10.2.1节给出的定义实现

对于内部方法[[Construct]],必须使用ECMA规范10.2.2节给出的定义实现

Proxy对象的内部方法[[Get]]没有使用ECMA规范10.1.8给出的定义实现,所以Proxy是一个异质对象,因此代理对象与普通对象的区别是[[Get]]的实现,代理对象会在没有指定对应拦截函数,例如没有指定get函数,当我们通过代理随性访问属性值时,代理对象内部方法[[Get]]会调用原始对象内部方法[[Get]]来获取属性值,这其实就是代理透明性质

使用Reflect:

Reflect对象的设计目的有这样几个。

(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。

(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。

(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

在响应式中使用Reflect的原因:

假如没有使用Reflect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj={
foo:1,
get bar(){
return this.foo
}
}
const p= new Proxy(obj,{
get(target,key){
track(target,key)
return target[key]
},
set(targte,key,newVal,receiver){
target[key]=newVal;
trigger(target,key)
}
})

当在effect函数中通过代理对象p访问bar属性

1
2
3
effect(()=>{
console.log(p.bar)
})

当effect注册的副作用函数执行时,会读取p.bar属性,它发现p.bar是一个访问器属性,因此执行getter函数,由于在getter函数中通过this.foo读取了foo的属性值,因此我们认为副作用函数与属性foo之间会建立联系,当我们修改p.foo的值时却没有使得副作用函数重新执行

原因在于this.foo中的this指向的是target,在代理对象中get函数返回的target[key]相当于obj.bar,在副作用函数中通过原始对象访问它的某个属性不会触发响应,使用Reflect;

1
2
3
4
5
6
7
8
9
10
11
12
const p= new Proxy(obj,{
get(target,key){
track(target,key)
return Reflect.get(target,key)
},
set(targte,key,newVal,receiver){

Reflect.set(target,key,newVal,receiver)
trigger(target,key)
return true
}
})

receiver代表谁在读取属性,这里就是代理对象p,访问器属性bar的getter函数内this指向代理对象p,这会在副作用函数与响应式数据之间建立响应联系,创建代理对象时指定的拦截函数,实际上是用来自定义代理对象本身的内部方法和行为的,不是用来指定被代理对象的内部方法和行为