Proxy 与 Reflect
ES6 添加内置对象 Proxy 和 Reflect , 允许开发人员拦截并定义基本语言操作的自定义行为。
- 代理 Proxy 可以拦截 JS 引擎内部目标的底层对象操作,然后触发特定操作的陷阱函数。
- Reflect 对象方法的默认特性与相同底层操作一致。
Proxy 构造函数
let proxy = new Proxy(target, handler)
target 指代理的对象。handler 指处理程序(定义一个或多个陷阱的对象)。
Proxy 可用陷阱
| 代理陷阱 | 拦截行为 | 默认特性 |
|---|---|---|
| has | in 操作符 | Reflect.has() |
| get | 属性访问:proxy[foo]、proxy.bar |
Reflect.get() |
| set | 属性赋值:proxy[foo] = bar、proxy.foo = bar | Reflect.set() |
| deleteProperty | delete 操作符 | Reflect.deleteProperty() |
| getPrototypeOf | Object.getPrototypeOf() __proto__ Object.prototype.isPrototypeOf() instanceof |
Reflect.getPrototypeOf() |
| setPrototypeOf | Object.setPrototypeOf() | Reflect.setPrototypeOf() |
| isExtensible | Object.isExtensible() | Reflect.isExtensible() |
| preventExtensions | Object.preventExtensions() | Reflect.preventExtensions() |
| getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() | Reflect.getOwnPropertyDescriptor() |
| defineProperty | Object.defineProperty() | Reflect.defineProperty() |
| ownKeys | Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() Object.assign() |
Reflect.ownKeys() |
| apply | proxy(..args) Function.prototype.apply() Function.prototype.call() |
Reflect.apply() |
| construct | new proxy(...args) | Reflect.construct() |
简单使用
has 陷阱
let target = {
value1: "aaa",
value2: "bbb"
};
let proxy = new Proxy(target, {
has(target, key){
if(key === "value2"){
return false;
}
return Reflect.has(target, key);
}
});
console.log("value1" in proxy); // true
console.log("value2" in proxy); // false
当使用 in 操作符都会调用 has 陷阱,并传入两个参数:target(代理目标),key(访问的属性键,字符串或 Symbol )。Reflect.has()也接受这些参数并返回 in 操作符默认响应。Reflect API接受的参数与相对应的陷阱参数相同。
get
let target = { value: "aaa" };
let proxy = new Proxy(target, {
get(target, key, receiver){
console.log(typeof key);
if(key in receiver){ // 由于 in 操作符会触发代理的
return Reflect.get(target, key, receiver);
}
throw new TypeError(key+"属性不存在!");
}
});
console.log(proxy.value); // "aaa"
console.log(proxy.value1); // 抛出错误
console.log(proxy[0]);
/*
输出结果:"string", 抛出错误
注意 get 陷阱 key 类型只能是字符串或 Symbol,所以对于数组的拦截操作记得转换 key 的类型
*/
get 陷阱拦截属性读取操作,传入3个参数:target(代理目标),key(读取的属性键,字符串或Symbol),receiver(操作的对象,一般指代理)。一般访问不存在的属性是 undefined,通过 get 陷阱抛出类型错误。
set
let target = { _value: "aaa" };
let proxy = new Proxy(target, {
set(target, key, value, receiver){
if(key[0]==="_"){
return false;
}
return Reflect.set(target, key, value, receiver);
}
});
proxy._value = "bbb";
console.log(proxy._value); // "aaa"
示例通过 set 陷阱使得不能对代理目标下划线开头的属性进行修改,通常下划线开头属性为私有属性。
deleteProperty
let proxy = new Proxy({ name: "target" }, {
deleteProperty(target, key){
if(key === "name"){
return false;
}
return Reflect.deleteProperty(target, key);
}
});
delete proxy.name;
console.log(proxy.name); // "target"
拦截 delete 操作,传入参数:target(代理目标),key(删除的属性键,字符串或Symbol)。
原型代理陷阱
原型代理陷阱对返回值有限制:
- getPrototypeOf 陷阱返回的必须是对象或 null,只要返回值必将导致运行错误。
- setPrototypeOf 陷阱返回为 false,表示操作失败,Object.setPrototypeOf()会抛出错误,如果返回任何不是 false 的值,那么 Object.setPrototypeOf() 操作成功。
let target = {};
let proxy = new Proxy(target, {
getPrototypeOf (target) {
return null;
},
setPrototypeOf (target, proto) {
return false;
}
});
console.log(Object.getPrototypeOf(proxy); // null
Object.setPrototypeOf(proxy, {}); // 抛出错误
construct
function fun () {};
let proxy = new Proxy(fun, {
construct(target, argumentsList){
argumentsList.forEach(value => {
if(typeof value !== "number"){
throw new TypeError("所有参数必须为数字!");
}
});
}
});
let object = new proxy(1,2,"3"); // 抛出TypeError: 所有参数必须为数字!
通过 new 操作符调用类构造函数,就会触发 construct 陷阱,拦截操作并检查参数。
apply
function person (name) {
this.name = name;
}
let proxy = new Proxy(person, {
apply(target, thisArg, argumentsList){
throw new TypeError("必须使用 new 来调用!");
}
});
proxy("aaa"); // 抛出TypeError: 必须使用 new 来调用!
普通执行、Function.prototype.apply()、Function.prototype.call() 3个方式调用函数都会触发 apply 陷阱,并传入3个参数:target(代理目标),thisArg 函数被调用时内部this的值,argumentsList 执行函数的参数。
后续
简单认识下 Proxy、Reflect,在以后项目中使用到会第一时间记录下来。