Proxy是什么?
Proxy顾名思义是代理,那么代理又是什么?
- 代理是目标对象的抽象,可以当目标对象的替身,但是完全独立于目标对象。目标对象可以独立操作,也可以通过代理来操作。
- 打个比方,目标对象相当于银行卡主卡,代理就是银行卡的副卡,可以通过目标对象主卡进行存钱以及消费行为,也可以通过代理副卡进行存钱和消费。副卡进行的一切操作都会反应到主卡的账户上。 备注:当然这个比方并不是完全恰当,但好理解
Proxy语法
const p = new Proxy(target, handler);
// target 是要代理的目标对象
// handler 定义了基本操作的捕获器,用一个对象包起来
创建一个没有任何附加功能的代理
最简单的代理就是空代理,它只做一件事,那就是在代理对象上执行的所有操作都无条件反射到目标对象上去。 所以任何目标对象可以做的事,完全可以交给代理去做,目标对象休息就行。
const target = {name: 'zorana'};
const handler = {};
const p = new Proxy(target, handler);
// 通过代理可以访问目标对象的属性name
console.log(p.name); // zorana
console.log(target.name); // zorana
// 给目标对象的属性赋值会反应在代理对象上,因为两个对象都是访问的同一个值
target.name = 'zorana1';
console.log(target.name); // zorana1
console.log(p.name); // zorana1
// 给代理对象赋值会反应在目标对象上,因为这个赋值会反射到目标对象
p.name = 'zorana2';
console.log(target.name); // zorana2
console.log(p.name); // zorana2
//hasOwnProperty()方法在在代理上操作也是应用到目标对象
console.log(target.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('name')); // true
从上面可以看出,代理的目标对象和代理是共享属性,共享方法,共享操作。但是代理并不是目标对象,是完全独立于目标对象的。只是代理进行的一切操作都会反射到目标对象上。
捕获器trap
既然我们能直接操作目标对象,为啥非要给目标对象设置一个代理呢?
- 其实我们使用代理的原因就是我们可以自己定义捕获器。Proxy的第二个参数handler对象中,可以定义一系列的捕获器,get,set,deleteProxy等等,利用这些捕获器来捕获到代码中对属性进行获取,赋值,删除等操作的时机。我们获取到这个时机,比如set,可以获取到对属性进行赋值的时机,我们可以在这个时机进行拦截自定义一些自己的业务逻辑。
- 因为我们对目标对象的操作改为了对代理对象相同属性的操作,所以最后需要用Reflect执行目标对象的原始操作,也就是代理对象的操作反射到目标对象上去。 备注:只有通过代理执行的相应操作才会被捕获到,原目标对象直接的操作是不会被捕获到的!
基本操作
handler.get
对属性读取操作的拦截
var obj = {
count: 1,
}
var p = new Proxy(obj, {
get: function (target, key, receiver) {
let sucess = Reflect.get(target, key, receiver);
if(sucess) {
console.log('Proxy.get', target, key, receiver);
}
return sucess;
},
});
p.count; // Proxy.get obj count p // 真实值 Proxy.get {count: 1} count Proxy {count: 1}
p['count']; // Proxy.get obj count p // 真实值 Proxy.get {count: 1} count Proxy {count: 1}
Reflect.get(p, 'count'); //Proxy.get obj count p // 真实值 Proxy.get {count: 1} count Proxy {count: 1}
var subP = Object.create(p);
subP['count']; // Proxy.get obj count subP // 真实值 Proxy.get {count: 1} count {}
// subP并没有count属性,但是会调用原型上的get方法获取
handler.set
对属性赋值操作的拦截
var obj = {
count: 1,
}
var p = new Proxy(obj, {
set: function (target, key, value, receiver) {
let sucess = Reflect.set(target, key, value, receiver);
if(sucess) {
console.log('Proxy.set', target, key, value, receiver);
}
return sucess;
},
});
p.count = 2; // Proxy.set obj count 2 p // 真实值 Proxy.set {count: 2} count 2 Proxy {count: 2}
p['count'] = 3; // Proxy.set obj count 3 p // 真实值 Proxy.set {count: 3} count 3 Proxy {count: 3}
console.log(obj.count); //3
Reflect.set(p, 'count', 4); // Proxy.set obj count 4 p // 真实值 Proxy.set {count: 4} count 4 Proxy {count: 4}
var subP = Object.create(p);
subP['count'] = 5; // Proxy.set obj count 5 subP // 真实值 Proxy.set {count: 4} count 5 {count: 5}
subP['count'] = 6;// <无输出>
subP['count'] = 6会无输出,对一个普通对象subP的未定义属性count赋值时,会执行到原型链上的Proxy的set拦截器,同时subP会有一个新增的属性count。第二次向subP赋值时,由于已经存在属性count,就不会访问到原型链上的Proxy,也就不会执行Proxy中的set逻辑。
handler.deleteProxy
对delete操作的拦截
var obj = {
count: 1,
age: 18,
}
var p = new Proxy(obj, {
deleteProperty(target, key) {
let sucess = Reflect.deleteProperty(target, key);
if(sucess) {
console.log('Proxy.delete', target, key);
}
return sucess;
},
});
delete p.age; // Proxy.delete {count: 1} age
delete p.count; // Proxy.delete {} count
handler.has
对 in 操作符的拦截
var obj = {
count: 1,
}
var p = new Proxy(obj, {
has: function(target, key) {
console.log('Proxy.has', key, target === obj);
return Reflect.has(target, key);
},
});
'count' in p; // Proxy.has count true
var subP = Object.create(p);
'count' in subP; // Proxy.has count true
Reflect.has(p, 'count'); // Proxy.has count true
with(p) {
(count); // Proxy.has count true
}
handler.apply
对函数调用的拦截
function fn() { }
var pfn = new Proxy(fn, {
apply: function (target, thisArg, argumentsList) {
console.log('Proxy.apply', target, thisArg, argumentsList);
return Reflect.apply(target, thisArg, argumentsList);
},
});
pfn(1); // Proxy.apply fn undefined [1]
pfn.apply({}, [2]); // Proxy.apply fn {} [2]
pfn.call({}, 3); // Proxy.apply fn {} [3]
执行pfn.apply({},[2]);时,会先执行.操作获取apply 函数,所以Proxy上的get拦截器会先执行
其他捕获器
// 在读取代理对象的原型时触发该操作,比如在执行 Object.getPrototypeOf(proxy) 时。
handler.getPrototypeOf()
// 在设置代理对象的原型时触发该操作,比如在执行 Object.setPrototypeOf(proxy, null) 时。
handler.setPrototypeOf()
// 在判断一个代理对象是否是可扩展时触发该操作,比如在执行 Object.isExtensible(proxy) 时。
handler.isExtensible()
// 在让一个代理对象不可扩展时触发该操作,比如在执行 Object.preventExtensions(proxy) 时。
handler.preventExtensions()
// 在获取代理对象某个属性的属性描述时触发该操作,比如在执行 Object.getOwnPropertyDescriptor(proxy, "foo") 时。
handler.getOwnPropertyDescriptor()
// 在定义代理对象某个属性时的属性描述时触发该操作,比如在执行 Object.defineProperty(proxy, "foo", {}) 时。
andler.defineProperty()
// 在判断代理对象是否拥有某个属性时触发该操作,比如在执行 "foo" in proxy 时。
handler.has()
// 在读取代理对象的某个属性时触发该操作,比如在执行 proxy.foo 时。
handler.get()
// 在给代理对象的某个属性赋值时触发该操作,比如在执行 proxy.foo = 1 时。
handler.set()
// 在删除代理对象的某个属性时触发该操作,比如在执行 delete proxy.foo 时。
handler.deleteProperty()
// 在获取代理对象的所有属性键时触发该操作,比如在执行 Object.getOwnPropertyNames(proxy) 时。
handler.ownKeys()
// 在调用一个目标对象为函数的代理对象时触发该操作,比如在执行 fn() 时。
handler.apply()
// 在给一个目标对象为构造函数的代理对象构造实例时触发该操作,比如在执行new User() 时。
handler.construct()