一、什么是Proxy代理器
ES6新增的代理提供了拦截并向基本操作嵌入额外行为的能力,具体意思是可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。
Proxy可以理解成, 在目标对象之前设置一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
初识代理器
可以通过提供的Proxy构造函数,用来生成Proxy实例
{
// 第一个参数为代理的对象,第二个参数也是一个对象,对拦截的处理
let obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting${propKey}`);
// console.log(target);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting${propKey}`);
// console.log(value);
return Reflect.set(target, propKey, value, receiver)
}
})
obj.count = 1;
++obj.count;
console.log(obj);
}
创建Proxy语法
- new Proxy(target,handler)
Proxy对象的所有用法,都是这种形式,不同的知识handler参数的写法
- new Proxy()表示生成一个Proxy实例
- target参数表示所要拦截的目标对象
- handler参数也是一个对象,用来定制拦截行为
拦截读取属性行为
要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对代理的对象(目标对象)进行操作
{
// 拦截读取属性行为
let proxy = new Proxy({},{
get(target,propKey){
return 35
}
})
console.log(proxy.name);
console.log(proxy.time);
console.log(proxy.age); // 都为35
}
如果没有对handler设置任何拦截,那就等同于直接通向目标对象
{
// 如果没有设置拦截器,那就直接通向原对象
let target = {};
let handler = {};
let p = new Proxy(target,handler)
p.a = 'b';
console.log(target,p.a); // { a: 'b' } b
target.a = 'bbb';
console.log(target,p.a); // { a: 'bbb' } bbb
}
Proxy代理器也可以设置到对象的属性上,这样可以再object对象上调用
{
// 可以将Proxy对象设置到object.proxy属性,从而可以再object对象上调用
// 需要时直接调用
{
let obj = {
proxy:new Proxy(this,{
get(target,prop){
console.log(`getting:${prop}`);
return target[prop] ?? 'Not Found'
}
}),
}
let x = obj.proxy.a; // 通过代理,getting:a
console.log(x); // Not Found
}
}
同一个拦截器对象,也可以设置拦截多个操作
{
// 同一个拦截器对象,可以设置拦截多个操作
let handler = {
// 设置属性时拦截
get(target,name){
if(name == 'prototype'){
return Object.prototype
}
return `Hello,${name}`
},
// 函数调用时拦截
apply(target,thisBinding,args){
return args[0]
},
// 创建构造函数时拦截
construct(target,args){
let obj = new target(...args);
obj.name = 'foo';
obj.value = args[1];
return obj;
}
}
let fproxy = new Proxy(function(x,y){return x + y},handler)
console.log(fproxy(1,2)); // 1
console.log(new fproxy(1,2)); // { name: 'foo', value: 2 }
console.log(fproxy.prototype === Object.prototype); //true
console.log(fproxy.age); // Hello,age
}
Proxy支持的拦截操作一共有13种
- get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy[‘foo’]。
- set(target, propKey, value, receiver):拦截对象属性的设置,比如- - proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
- has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for…in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(…args)。
常用的只有get()和set()方法
二、Proxy实例方法
get()
用于拦截某个属性的读取操作,接受三个参数,依次为目标对象、属性名和proxy实例本身,其中最后一个为可选参数
使用get拦截,实现数组读取负数的索引
{
// 使用代理拦截实现数组的负索引
// 第三个参数receiver就是这个代理器的实例
function createArray(...elements){
let handler = {
get(target,propKey,receiver){
let index = Number(propKey);
if(index < 0){
propKey = String(target.length + index);
}
return target[propKey]
}
}
let target = [];
target.push(...elements);
return new Proxy(target,handler)
}
let arr = createArray('a','b','c','d','e');
console.log(arr[-2]); // d
}
如果一个属性不可配置且不可写则Proxy不能修改该属性,否则通过Proxy对象访问属性会报错
{
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false,
},
});
const handler = {
get(target, propKey) {
return "abc";
},
};
const oneProxy = new Proxy(target, handler);
console.log(oneProxy.foo);
}
foo属性是只读的并且不可配置的,因此不能代理该属性。
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和Proxy实例本身,最后一个也是可选的参数,返回一个布尔值
假设Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的试训过值符合要求
{
let validator = {
set(obj,prop,value){
if(prop === 'age'){
if(!Number.isInteger(value)){
throw new TypeError('the age must be an integer')
}
if(value > 200){
throw new RangeError('the age seems invalid')
}
}
obj[prop] = value
return true
}
}
let person = new Proxy({},validator);
// 可以使用try catch来捕获异常
try {
person.age = 250;
console.log(person.age);
} catch (error) {
console.log(error); // 如果大于200会报错
}
console.log('end');
}