ES6+ Proxy与 Reflect 使用
说明
Proxy 译为 “代理器”,用于修改某些操作的默认行为,可以认为是在目标对象|函数之前架设一层“拦截”,外接的访问,都必须通过这层拦截,这层拦截中可以对外接的访问进行过滤和改写
1. 基本语法
- target 指被代理的目标对象或函数
- handler 定制的用来拦截外界操作行为的对象
- traps 对应外界操作的拦截方法
- new Proxy() 用来创建一个 proxy 实例
const handler = {
[traps](...args) {
...
}
};
const obj = new Proxy(target, handler)
2. 支持的拦截操作
get(target, key, receiver)
拦截读取操作set(target, key, value, receiver)
拦截赋值操作has(target, key)
拦截 in 操作符操作(for in 循环不算)deleteProperty(target, key)
拦截 delete 操作符操作ownKeys(target)
拦截Object.getOwnPropertyNames(proxy) | Object.getOwnPropertySymbols(proxy) | Object.keys(proxy)
这三个获取键名的操作getOwnPropertyDescriptor(target, key)
拦截获取属性描述符的操作Object.getOwnPropertyDescriptor(proxy, key)
defineProperty(target, key, description)
拦截定义属性操作符的操作Object.defineProperty(proxy, key, description) | Object.defineProperties(proxy, descriptions)
preventExtensions(target)
拦截将对象变为不可扩展的操作Object.preventExtensions(proxy)
isExtensible(target)
拦截判断对象是否是可扩展的操作Object.isExtensible(proxy)
getPrototypeOf(target)
拦截返回原型对象的操作,Object.getPrototypeOf(proxy)
apply(target, context, args)
拦截 target 为函数时,调用函数的操作 包括:fn() | fn.call() | fn.apply()
construct(target, args)
拦截以 Proxy 实例为构造函数调用的操作new Fn()
const def = {
_def: 'def obj'
};
const proxy = new Proxy(def, {
// 拦截取值操作
get(target, key) {
// get拦截需要有返回值
return key in target ? target[key] : `target: ${JSON.stringify(target)} Not found key: ${key}`;
},
// 拦截赋值操作
set(target, key, value) {
if (key === 'age') {
return console.warn('age is a secret');
}
target[key] = value;
},
// 拦截 in 操作符操作
has(target, key) {
if(key[0] === '_') {
return false;
}
return key in target;
},
// 拦截 delete 操作,抛出错误或者返回 false 都无法删除
deleteProperty(target, key) {
if (key[0] === '_') {
console.warn(`Cant delete prop: ${key}`);
return false;
}
console.log('delete', key);
return true;
},
// 拦截 Object.keys() 等获取属性名操作, 返回数组的每一项如果 proxy 中不存在这个属性则会被过滤掉
ownKeys(target) {
// proxy 中没有 a 属性所以即使写在返回值中也不会被返回
return ['a', '_def'];
}
});
// 取值测试
console.log(proxy.color); // target: {"_def":"def obj"} Not found key: color
// 赋值测试
proxy.age = 90; // age is a secret
proxy.name = '小白';
console.log(JSON.stringify(proxy)); // {"_def":"def obj","name":"小白"}
// in 测试
console.log('_def' in proxy); // false
for (key in proxy) {
console.log(key); // _def name has 拦截对 for-in 不生效
}
// delete 测试
delete proxy._def; // Can't delete prop: _def
// Object.keys() 测试
console.log(Object.keys(proxy)); // ["_def"]
/* =========== ============ ============ ============ =========== */
const def = function (a, b) {
return a * b;
};
const Def = new Proxy(def, {
// 拦截函数调用
apply(target, context, args) {
console.log(context, args, target(...args));
return target(...args);
},
// 拦截构造函数调用
construct(target, args) {
if (args.length < 2) {
console.warn('The arguments is not enough')
return {value: null};
}
return {value: target(...args)};
}
});
// 测试函数调用
Def(90, 20); // undefined [90, 20] 1800
const ctx = {name: 'ctx'};
Def.call(ctx, 90, 20, 30); // {name: "ctx"} [90, 20, 30] 1800
// 测试构造函数调用
new Def(79); // The arguments is not enough
console.log(new Def(89, 27)); // {value: 2403}
3. Reflect 反射
Reflect 对象的方法与 Proxy 对象的方法一一对应,这样无论在 Proxy 上怎么修改默认行为,都可以通过调用 Reflect 方法来完成默认行为
const def = {
_def: 'def'
};
const proxy = new Proxy(def, {
get(target, key) {
console.log(target, target[key]);
return Reflect.get(target, key); // 触发默认操作
}
});
console.log(proxy._def); // def