一文了解你所不知道的Proxy💯前言🐼 这几天准备开始看Vue3相关的源码。针对这个Proxy而言的话,自己理解的不
前言🐼
这几天准备开始看Vue3
相关的源码。针对这个Proxy而言的话,自己理解的不是特别清楚,故今天来彻底且系统的去弄清楚。
Proxy🐕
概念:Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
语法:const p = new Proxy(target, handler)
target
:要使用Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。简而言之就是需要劫持的对象。handler
:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理p
的行为。(也就是定义一大堆的捕获器,去捕获对于对象的操作行为)。
先上代码:
//定义对象分别有name、age两个属性
let target = {
name:"xxxx",
age:20
}
//定义相关捕获器
const handler = {
set(target,key,value){
console.log("触发了set方法,key:"+key);
target[key] = value;
return true;
},
get(target,key){
console.log("触发了get方法,key:"+key)
return target[key];
}
}
//创建Proxy对象
const proxy = new Proxy(target,handler);
proxy.name="1111" // 打印 “触发了set方法,key:name”
proxy.address="China" //打印 “触发了set方法,key:address”
console.log(proxy.name); // 打印 “触发了get方法,key:name”
如上述例子所示,我们在初始化Proxy
之后,利用Proxy
对象进行访问相关属性,便能通过相关捕获器来捕获对象的各类操作。以便去做一些其他的事情。例如我们上述例子中可以在里面去进行年龄的相关校验。
需要注意的是set
捕获器应当返回一个布尔值,- 在严格模式下,如果 set()
方法返回 false
,那么会抛出一个 TypeError
异常。
当然,对于我们的Proxy也可以去代理数组对象。它的使用方式也如上述例子相同。
let target = ["红楼梦","水浒传","三国演义"]
const handler = {
set(target,key,value){
console.log("触发了set方法,key:"+key)
target[key] = value;
return true;
},
get(target,key){
console.log("触发了get方法,key:"+key)
return target[key];
}
}
const proxy = new Proxy(target,handler);
//打印 “触发了get方法,key:0”
console.log(proxy[0]);
//打印 “触发了set方法,key:0”
proxy[0] = "西游记";
//打印
// 触发了get方法,key:push
// 触发了get方法,key:length
// 触发了set方法,key:3
// 触发了set方法,key:length
proxy.push("西游记")
这里有人可能会有相对应的疑惑:为啥get
与set
都会触发两次呢?
你仔细看实际会发现,它实际上是push
内部做了一点事情,被全部捕获出来了。除了第一次触发的get方法,其他的都是对于数组的length属性的访问。
像Proxy
其实除了set
、get
两个对于属性访问的捕获器之外,还有其他类型的捕获器,这里去MDN上查看,所有针对对象的操作,都大差不差有相对应捕获器来支持,此处不做太多详细说明。
另外还有一个点需要探讨,就是Proxy对象中的this
指向问题。
一般来说,我们利用Proxy
对象去进行代理时,我们会在他的捕获器中去做一些相关操作,例如我们熟知的Vue
在set
与get
中进行了相对于的依赖收集和派发更新。所以在Proxy
以及对象中也会有对于相关this
的使用场景。那么,在Proxy
中this指向是怎么样的呢?
废话不多说,直接上代码。
let target = {
name:"xxxx",
age:20
}
const handler = {
set(target,key,value){
console.log("触发了set方法,key:"+key)
console.log(this);
target[key] = value;
return true;
},
get(target,key){
console.log("触发了get方法,key:"+key)
return target[key];
}
}
const proxy = new Proxy(target,handler);
proxy.name="1111"
proxy.address="China"
console.log(proxy.name);
在上述例子中,我们在set
捕获器内打印this
。
它的打印结果是:{ set: [Function: set], get: [Function: get] }
。那也就是说,在Proxy捕获器内,this
他的指向为当前这个handler
捕获器对象。
好!!!!我们再看下对象内的this指向:
let target = {
name:"xxxx",
age:20,
show(){
console.log(this === proxy) //true
}
}
const handler = {}
const proxy = new Proxy(target,handler);
proxy.show()
如图所示,我们的对象内this
指向为Proxy
。
好🙉!!!! 那么问题来了。
既然proxy
代理的对象,对象内部this指向会指向proxy
对象。那么请看下面例子。
let map = new WeakMap();
let target = {
name:"xxxx",
age:20,
saveValue(){
map.set(this,{name:this.name,age:this.age});
}
}
const handler = {}
const proxy = new Proxy(target,handler);
proxy.saveValue();
console.log(map.get(target)) //undefined
console.log(map.get(proxy)) //{ name: 'xxxx', age: 20 }
这里详细解释一下:首先,map.get(target)
返回的是undefined
。此例子通过saveValue
将元素属性以this为键,name以及age为值保存到WeakMap中。但是由于this指向为proxy,所以在外部利用map.get(target)
获取的为undefined
,而map.get(proxy)
获取我们存进去的值。
正因为如此,Proxy对于原生属性代理也是一样!!
const handler = {}
const proxy = new Proxy(new Date(),handler);
console.log(proxy.getDate()) //TypeError: this is not a Date object.
getDate方法只能在Date对象实例上面拿到,如果this
不是Date对象实例就会报错。this
绑定原始对象,就可以解决这个问题。
像这样子:
const handler = {get(target,key){
if (key === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, key);
}}
const proxy = new Proxy(new Date(),handler);
console.log(proxy.getDate())
对于我们的捕获器而言,相信看过上面的MDN相关文档,会发现它还有着另外一个参数 receiver
,那么这个参数是干嘛的呢?
let target = {
name:"xxxx",
age:20,
}
const handler = {
set(target,key,value){
target[key] = value;
return true;
},
get(target,key,receiver){
console.log(receiver === this); //fasle
console.log(receiver === target) //fasle
console.log(receiver === proxy) //true
return target[key];
}
}
const proxy = new Proxy(target,handler);
proxy.age;
该例子,我们在get函数中设置了三个等式,我们在上述代码中可以看出,这个receiver也是我们的proxy
对象,但是这种说法并不太准确。为啥子呢?请看下面例子。
const parent = {
get value() {
return 'xiafighting';
},
};
const handler = {
get(target, key, receiver) {
console.log(receiver === proxy); //false
console.log(receiver === obj); //true
return target[key];
},
}
const proxy = new Proxy(parent,handler);
const obj = {
name: 'xxxx',
};
Object.setPrototypeOf(obj, proxy);
console.log(obj.value) //xiafighting
解释一下:这里我们创建了一个parent对象与他的代理对象。最后将创建好的obj对象原型链设置成为代理对象。我们可以看到结果receiver === proxy
结果为false,而receiver === obj
结果为true
,由此可以看出receiver
其实就是为了正确的在捕获器中传递上下文(简单来说就是谁调this指向谁)。它不仅仅指向proxy
对象。但是也不要和this
搞混淆,捕获器中的this
,是指向捕获器本身的。这里可以看上面的例子。
好啦,整个Proxy就已经完毕啦!!
总结
proxy
可以用来进行对象代理,它里面定义了一大堆捕获器进行捕获对象操作。proxy
所代理的对象中,this指向proxy
对象。proxy
捕获器中,this指向该捕获器对象,而捕获器中receiver
参数能更好的帮助我们指向正确的上下文对象。
好啦,讲到这里就结束啦。如果你觉得我讲解的有问题,欢迎提点诺!!!!! 最后如果觉得可以的话,给我留一个❤️❤️❤️❤️❤️!!!
转载自:https://juejin.cn/post/7425603008404733963