一、Proxy 介绍
Proxy:用于创建一个对象的代理,从而实现基本操作的拦截和自定义
二、Proxy 使用
Proxy 为构造函数用于生成 Proxy 实例
const proxy = new Proxy(target, handler)
// target 表示所要拦截的目标对象(任何类型的对象:原生数组,函数,甚至另一个代理)
// handler 通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时的代理行为
参数 handler 拦截属性
- get(target,propKey,receiver):拦截对象属性的读取
- set(target,propKey,value,receiver):拦截对象属性的设置
- has(target,propKey):拦截
propKey in proxy
的操作,返回一个布尔值 - deleteProperty(target,propKey):拦截
delete proxy[propKey]
的操作,返回一个布尔值 - ownKeys(target):拦截
Object.keys(proxy)
、for...in
等循环,返回一个数组 - getOwnPropertyDescriptor(target, propKey):拦截
Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象 - defineProperty(target, propKey, propDesc):拦截
Object.defineProperty(proxy, propKey, propDesc)
,返回一个布尔值 - 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 实例作为函数调用的操作
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作
Reflect 介绍
ES6 中操作对象而提供的新 API,若需要在 Proxy 内部调用对象的默认行为,建议使用 Reflect
基本特点:
- 只要 Proxy 对象具有的代理方法,Reflect 对象全部具有,以静态方法的形式存在
- 修改某些 Object 方法的返回结果,定义不存在属性行为的时候不报错返回false
- 让 Object 操作都变成函数行为
get(target, propKey, receiver)接受三个参数,为目标对象,属性名,proxy 实例本身
// get 对数组增删改查进行拦截,数组读取负数的索引
const createArray = (...element) => {
const handler = {
get(target, propKey, receiver) {
const index = Number(propKey);
if (index < 0) {
propKey = target.length + index + '';
}
return Reflect.get(target, propKey, receiver)
}
}
let target = [];
target.push(...element);
return new Proxy(target, handler)
}
const arr = createArray('z', 'd', 'x');
const lastEle = arr[-1];
console.log(lastEle);
set(target, propKey, value, receiver)用来拦截某个属性的赋值操作,接受四个参数,为目标对象,属性名,属性值,proxy 实例本身
// 定义一个对象,规定年龄输入整数时才被赋值,访问无效属性时控制台提醒
const person = {
name: 'zdx',
age: 18,
}
const proxy = new Proxy(person, {
get(target, propKey, receiver) {
if (propKey in target) return Reflect.get(target, propKey, receiver);
else {
console.warn('对象没有此属性');
return false;
}
},
set(target, propKey, value, receiver) {
if (propKey === 'age') {
if (typeof value === 'number' && /(^[1-9]\d*$)/.test(value)) {
return Reflect.set(target, propKey, value, receiver);
} else {
console.warn('年龄只能输入正整数');
return fasle;
}
} else {
return Reflect.set(target, propKey, value, receiver)
}
}
})
proxy.age = 20;
console.log(proxy.age); // 20
proxy.age = 21.001;
console.log(proxy.age); // 20
console.log(proxy.zdx); // false
deleteProperty(target, propKey)用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性无法被 delete 命令删除
const handler = {
deleteProperty(target, propKey) {
if (propKey[0] === '_') {
throw new Error('无法删除私有属性');
}
Reflect.deleteProperty(target, propKey);
return true;
}
};
const target = {
_prop: 'test',
};
const proxy = new Proxy(target, handler);
delete proxy._prop; // Error:无法删除私有属性
取消代理
Proxy.revocable(target, handler);
测试案例
const user = {
_name: 'Guest',
get name() {
return this._name;
}
}
const userProxy = new Proxy(user, {
get(target, propKey, receiver) {
return Reflect.get(target, propKey, receiver);
}
});
const admin = {
__proto__: userPoxy,
_name: 'Admin',
};
console.log(admin.name); // Guest
// admin 自身不具备 name 属性,顺着原型链往上找 name,
// user 对 name 的访问返回当前对象的 _name 属性,
// userProxy 代理的是 user 对象,所以 this 指向 user 对象,而不是 admin 对象
三、使用场景
Proxy 功能类似于设计模式中的代理模式,功能如下:
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
// 声明一个私有的 _apiKey,便于 api 这个对象内部的方法调用,但不希望对外部也访问 api._apiKey let api = { _apiKey: 'zdxtesttest', }; const arr = ['_apiKey']; api = new Proxy(api, { get(target, propKey, receiver) { if (arr.indexOf(propKey) > -1) { throw new Error(`${propKey} 不可访问`); } return Reflect.get(target, propKey, receiver); }, set(target, propKey, value, receiver) { if (arr.indexOf(propKey) > -1) { throw new Error(`${propKey} 不可修改`); } return Reflect.set(target, propKey, value, receiver); } }) console.log(api._apiKey); api._apiKey = '123123';
// 通过 proxy 实现观察者模式 // 观察者模式:函数自动观察数据对象,一旦对象有变化,函数自动执行 // observable 函数返回一个原始对象的 proxy 代理,拦截赋值操作,触发充当观察者的各个函数 const queueObservers = new Set(); const observe = fn => queueObservers.add(fn); const observable = obj => new Proxy(obj, { set(target, propKey, value, receiver) { queueObservers.forEach(observer => observer()); return Reflect.set(target, propKey, value, receiver); } }) // 观察者函数都放进 Set 集合中,当修改 obj 的值时,会在set函数中拦截,自动执行 Set 集合中的观察者