什么是Proxy
Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
const p = new Proxy(targte, handler);
handler对象的方法
handler.getPrototypeOf()
MDN-handler.getPrototypeOf()
它是一个代理(Proxy)方法,当读取代理对象的原型时,该方法会被调用。
以下五种操作(方法/属性/运算符)可以触发JS引擎读取一个对象的原型;
- Object.getPrototypeOf()
- Reflectt.getPrototype();
- __protot__
- Object.prototype.isPrototypeOf()
- instanceof
如果遇到下面两种情况,JS引擎会抛出TypeError异常:
- getPrototypeOf()方法返回的不是对象也不是null。
- 目标对象是不可扩展的,且gePrototyoeOf()方法返回的原型不是目标对象本身的原型
参数
当getPrototypeOf方法被调用时,this指向的是它所属于的处理器对象
target被代理的目标对象
返回值
返回值必须是一个对象或者null
示例
基本用法
let obj = {};
let proto = {};
let handler = {
getPrototypeOf(target) {
console.log(target === obj); // true
console.log(this === handler); // true
return proto;
}
}
let p = new Proxy(obj, handler);
console.log(Object.getPrototypeOf(p) === proto); // true 执行这句时候,会输出三个 true
5种触发getPrototypeOf代理方法的方式
let obj = {};
let p = new Proxy(obj, {
getPrototypeOf(target) {
return Array.prototype;
}
});
console.log(
Object.getPrototypeOf(p) === Array.prototype, // true
Reflect.getPrototypeOf(p) === Array.prototype, // true
p.__proto__ === Array.prototype, // true
Array.prototype.isPrototypeOf(p), // true
p instanceof Array // true
)
两种情况下的异常
let obj = {}
let p = new Proxy(obj, {
getPrototypeOf(target) {
return 'foo';
}
});
Object.getPrototypeOf(p);
//Uncaught TypeError: 'getPrototypeOf' on proxy: trap returned neither object nor null
let obj2 = Object.preventExtensions({});
let p2 = new Proxy(obj2, {
getPrototypeOf(target) {
return {};
}
});
Object.getPrototypeOf(p2);
// Uncaught TypeError: 'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype
handler.setPrototype()
MDN-Proxy-handler.setPrototypeOf()
用于拦截Object.setPrototypeOf()
可以拦截一下操作:
- Object.setPrototypeOf()
- Reflect.setPrototypeOf()
如果违反了下列规则,则proxy将抛出一个TypeError
- 如果targte不可扩展,原型参数必须与
Object.getPrototypeOf(target)的值相同
参数
| 参数 | 描述 |
|---|---|
| target | 被拦截的目标对象 |
| prototype | 对象新原型或为null |
示例
如果我们不想为对象设置一个新的原型,handler的setPrototypeOf方法可以返回false,也可以抛出异常。
let p = new Proxy({}, {
setPrototypeOf(target, newProtot) {
return false;
}
});
Object.setPrototypeOf(p, {});
// Uncaught TypeError: 'setPrototypeOf' on proxy: trap returned falsish for property 'undefined'
Reflect.setPrototypeOf(p, {}); // return false
handler.isExtensible()
MDN-handler.isExtensible()
用于拦截对对象的Object.isExtensible();
该方法拦截目标对象的以下操作:
- Object.isExtensible()
- Reflect.isExtensible() 违背了以下约束,proxy会抛出TypeError:
- Object.isExtensible(proxy)必须同Object.isExtensible(target)返回同样的值。
示例
// 示例1
let obj = {
canEvolve: true
}
let proxy = new Proxy(obj, {
isExtensible(target) {
return Reflect.isExtensible(target);
},
preventExtensions(target) {
target.canEvolve = false;
return Reflect.preventExtensions(target)
}
});
console.log(Object.isExtensible(proxy)); // true
Object.preventExtensions(proxy);
console.log(Obect.isExtensible(proxy)); // false
console.log(obj.canEvolve); // false
// 示例2
let p = new Proxy({}, {
isExtensible(target) {
console.log('calleed');
return true;
}
});
console.log(Object.isExtensible(p)); // calleed true
以下代码演示违反约束
var p = new Proxy({}, {
isExtensible(target) {
return false; // return 0; return NaN 等都会报错
}
});
Object.isExtensible(p);
// Uncaught TypeError: 'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is 'true')
handler.preventextensions()
MDN-handler.preventextensions()
拦截Object.preventExtensions返回一个布尔值。
这个trap可以拦截:
- Object.preventEctensions()
- Object.preventExtensions()
如果违反了下列规则,proxy会抛出一个TypeError
- 如果目标对象可扩展的,返回false 与实际值不匹配
参数
target所要拦截的目标对象
示例
const monster1 = {
canEvolve: true
}
const proxy = new Proxy(monster1, {
preventExtnesions(target) {
target.canEvolve = false;
OBject.preventExtensions(target);
return true;
}
});
console.log(monster1.canEvolve); // true
Object.preventExtensions(proxy); // Proxy {canEvolve: true}
console.log(monster1.canEvolve); // true
handler.getOwnPropertyDescriptor()
MDN-handler.getOwnPropertyDescriptor()
它是Object.getOwnProtopertyDescriptor的陷阱。
这个陷阱可以拦截一下操作:
- Object.getOwnPropertyDescriptor()
- Reflect.getOwnPropertyDescriptor()
如果下列不变量被违反,代理将抛出一个TypeError:
getOwnPropertyDescriptor必须返回一个object或undefined- 如果属性作为目标对象的不可配置的属性存在,则改属性无法报告为不存在
- 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则不能将其报告为不存在。
- 如是属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在。
- 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置属性存在。
- Object.getOwnPropertyDescriptor(target)的结果可以使用Object.defineProperty应用于对象,也不会抛出异常。
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象 |
| prop | 反悔属性名称的描述 |
示例
let p = new Proxy({age: 24}, {
getOwnPropertyDescriptor(target, prop) {
console.log('called: ' + prop);
return Reflect.getOwnPropertyDescriptor(target, prop);
}
});
console.log(Object.getOwnPropertyDescriptor(p, 'age'));
// called: age
// {value: 24, writable: true, enumerable: true, configurable: true}
以下代码则违反了不变量
const obj = {
a: 10
}
Object.preventExtensions(obj);
var p = new Proxy(obj, {
getOwnPropertyDescriptor: function(target, prop) {
return undefined;
}
});
Object.getOwnPropertyDescriptor(p, 'a');
// Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned undefined for property 'a' which exists in the non-extensible proxy target
handler.defineProperty()
MDN-handler.defineProperty()
用于拦截对对象的Object.defineProperty()操作。
该方法会拦截目标对象的以下操作:
- Object.defineProperty()
- Reflect.defineProperty()
- Proxy.property='value'
如果违背以下的不变量,proxy会抛出TypeError
- 如果目标对象不可扩展,将不能添加属性
- 不能添加或者修改一个属性为不可配置的,如果它作为一个目标对象的不可配置的属性存在的话。
- 如果目标对象存在一个对应的可配置属性,那么
Object。defineProperty(target, prop, descriptor)将不会抛出异常 - 在严格模式下,
false作为handler.defineProperty方法的反悔值的话讲抛出TypeError异常。
示例
let p = new Proxy({}, {
defineProperty(target, prop, descriptor) {
console.log('called: ' + prop);
return true;
}
});
Object.defineProperty(p, 'a', {
configurable: true,
enumberable: true,
value: 10
})
当调用Object.defineProperty()或者Reflect.definedProperty(),传递给defineProperty的descriptor you一个现实-只有以下属性才有用,非标准的属性将会被无视。
- enumberable
- configurable
- writable
- value
- get
- set
handler.has()
MDN-Proxy-handler.has()
可以餐做是针对in操作的钩子。返回一个boolean属性的值
钩子可以拦截下面的这些操作
- 属性查询
foo in proxy - 继承属性查询
foo in Object.create(proxy) with检查:with(proxy) { (foo) }- Reflect.has(); 如果违反了下面的这些规则,proxy将会抛出TypeError
- 如果目标对象的某一些属性本身不可被配置,则改属性不能够被代理隐藏
- 如果目标对象为不可扩展对象,则对象的属性不能被代理隐藏。
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象 |
| has | 需要检查是否存在的属性 |
示例
const obj = {
_secret: 'easily scared',
count: 4
}
const p = new Proxy(obj, {
has(target, key) {
if(key[0] === '_') {
return false;
}
return key in target;
}
});
console.log('count' in p); // true
console.log('_secret' in p); // false
console.log('_secret' in obj); // true
下面的代码违反了约束:
let obj = {
a: 10
};
Object.preventExtensions(obj);
var p = new Proxy(obj, {
has(target, key) {
return false;
}
});
'a' in p;
// Uncaught TypeError: 'has' on proxy: trap returned falsish for property 'a' but the proxy target is not extensible
handler.get()
MDN-Proxy-handler.get()
拦截对象的读取属性的操作。可以返回任何值。
会拦截目标对象的以下的操作:
- 访问属性:
proxy[foo]和proxy.bar - 访问原型链上的属性:Object.create(proxy)[foo]
- Reflect.get();
如果违背了以下的约束。proxy会抛出TypeError
- 如果要访问的目标属性是不可以写以及不可配置的,则返回的值必须与该目标属性的值相同。
- 如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined。
参数
| 参数 | 描述 |
|---|---|
| targte | 目标对象 |
| property | 被获取的属性名 |
| receiver | Proxy或者继承Proxy的对象 |
示例
let p = new Proxy({}, {
get(target, prop, receiver) {
console.log('called: ' + prop);
return 10;
}
});
console.log(p.a);
// called: a
// 10
以下代码演示违反了约束的情况
let obj = {};
Object.defineProperty(obj, 'a', {
configurable: false,
enumberable: false,
value: 10,
writable: false
});
let p = new Proxy(obj, {
get(target, prop) {
return 20;
}
});
p.a;
// Uncaught TypeError: 'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')
handler.set()
MDN-Proxy-handler.set()
用于拦截设置属性值的操作。
该方法会拦截目标对象的以下操作:
- 指定属性值:
proxy[foo] = bar和proxy.foo = bar - 指定继承者的属性值:
Object.create(proxy)[foo] = bar - Reflect.set() 如果违背了以下约束条件,proxy会抛出TypeError异常
- 若目标属性是一个不可写及不可配置的数据属性,则不可以改变它的值
- 如果目标属性没有配置存储方法,即
[[Set]]属性的是undefined,则不能设置它的值。 - 在严格模式下,如果
set()方法反悔false,那么也会抛出TypeError异常
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象 |
| property | 将设置为属性名或Symbol |
| value | 新属性值 |
| receiver | 最初被调用的对象,通常是proxy本身,但handler的set方法也有可能在原型链上,或以其他方式被间接地调用(因此不一定是proxy本身) |
假设有一段代码执行
obj.name = 'jen',obj不是一个proxy,且自身不含name属性,但是他的原型链上有一个proxy,那么。那个proxy的set()处理器会被调用,而此时,obj回座位receiver参数传进来。
返回值
set()方法应当返回一个布尔值。
- 返回true代表属性设置成功
- 严格模式下,如果set()方法返回false,那么会抛出TypeError异常
示例
const obj = {
count: 4
}
let p = new Proxy(obj, {
set(obj, prop, value) {
if(prop === 'count' && (value % 2) !== 0) {
console.log('obj must have an even number');
}
else {
return Reflect.set(...arguments);
}
}
});
p.count = 1; // obj must have an even number
console.log(p.count); // 4
p.count = 8;
console.log(p.count); // 8
handler.deleteProperty()
MDN-Proxy-handler.deleteProperty() 方法可以拦截delete操作。 必须反悔一个Boolean值,表示该属性是否成功删除。 该方法会拦截以下操作:
- 删除属性:
delete proxy[foo]和delete proxy.foo - Reflect.deleteProperty()
如果违背了胰腺癌不变量,proxy将会抛出一个TypeError
- 如果目标对象的属性是不可配置的,那么该属性不能删除
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象 |
| property | 待删除的属性名 |
示例
let p = new Proxy({a: 10}, {
deleteProperty(target, prop) {
console.log('called: ' + prop);
return Reflect.deleteProperty(target, prop);
}
});
delete p.a; // called: a
handler.ownKeys()
MDN-Proxy-handler.ownKeys()
方法用于拦截Reflect.ownKeys()
该拦截器可以拦截以下操作:
- Object.getOwnPropertyNames();
- Object.getOwnPropertySymbols();
- object.keys();
- Reflect.ownKeys(); 如果违反了下面的约束,proxy将抛出错误TypeError:
- ownKeys的结果必须是一个数组
- 数据的元素类型要么是String,要么是Symbol
- 结果列表必须包含目标对象的所有不可配置(no-configuable)、自由(own)属性的key。
- 如果目标对象不可扩展,那么结果列表必须包含目标对象所有自由(own)属性的key,不能有其他的值。
示例
const obj = {
_age: 11,
[Symbol('secret')]: 'this is secret',
money: 4
}
const p = new Proxy(obj, {
ownKeys(target) {
return Reflect.ownKeys(target);
}
});
console.log(Object.keys(p)); // ['_age', 'money']
handler.apply()
MDN-Proxy-handler.apply()
用于拦截函数的调用。可以返回任何值。
方法或拦截目标对象的以下操作
- proxy(...args);
- Function.prototype.apply() 和 Function.prototype.call()
- Reflect.apply();
违反了以下约束,proxy将抛出TypeError - target必须是被调用的,也就是,它必须是一个函数对象。
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象(函数) |
| thisArg | 被调用时的上下文对象 |
| argumentList | 被调用是的参数数组 |
示例
let p = new Proxy(function() {}, {
apply(func, thisArg, argumentList) {
console.log('called:' + argumentList.join(','));
return Reflect.apply(func, thisArg, argumentList);
}
});
console.log(p(1, 2, 3));
// called:1,2,3
// undefined
handler.construct()
MDN-Proxy-handler.construct()
方法用于拦截new操作符。必须返回一个对象。
该拦截器可以拦截以下操作
- new Proxy(...agrs)
- Reflect.construct()
如果违反了以下约定,代理将会抛出错误TypeError
- 必须返回一个对象
参数
| 参数 | 描述 |
|---|---|
| target | 目标对象 |
| argumentList | constructor的参数列表 |
| newTarget | 最初被调用的构造函数 |
示例
function Foo(disposition) {
this.disposition = disposition;
}
const P = new Proxy(Foo, {
construct(target, args) {
console.log('Foo constructor called');
return new target(...args);
}
});
console.log(new P('fierce').disposition);
// Foo constructor called
// fierce