Proxy
基础知识
- 概念:用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程,即对编程语言进行编程.可以理解为,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写.Proxy的原义是代理,在这里表示由它来代理某些操作,可以译为代理器
- 一个简单的例子:
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
//上述代码说明,proxy实际上重载了点运算符,即用自己的定义覆盖了语言的原始定义
3. ES6原生提供proxy构造函数,用来生成proxy实例:var proxy=new Proxy(target,handler); target参数表示所要代理的目标对象,handler参数也是一个对象,用来定制拦截行为.注意:要使proxy起作用,必须针对proxy实例进行操作,而不是目标对象.如果handlerm没有设置任何拦截,就等于直接通向原对象
Proxy支持的拦截操作
- get(target,propKey,receiver):用于拦截对象属性的读取,比如proxy.foo和proxy['foo'],最后一个receiver是可选参数,详情参见Reflect.get的部分.这个target参数指的是new Proxy(target,handler)中的第一个参数
- set(target,propKey,value,receiver):拦截对象属性的设置,比如proxy.foo=v或proxy['foo']=v,返回一个布尔值
//假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用proxy保证age属性值符合要求 let validator={ set:function(target,key,value){ if(key=='age'){ if(!Number.isInteger(value)){ throw new TypeError('The age is not a integer'); } if(value>200){ throw new RangeError('The age seems invalid'); } target[key]=value; } } } let person=new Proxy({},validator); person.age=100; person.age ==>100 person.age='abc' ==>报错 person.age=300 ==>报错
- has(target,propKey):拦截propKey in proxy 的操作,返回一个布尔值.即判断对象是否具有某个属性时,这个方法会生效,典型的操作就是in操作符.如果原对象不可配置或禁止扩展,has拦截会报错
var handler={ has:function(target,key){ if(key[0]==='_'){ return false; } return key in target; } }; var target={_prop:'foo',prop:'foo'}; var proxy=new Proxy(target,handler); '_prop' in proxy // false
- deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值.目标对象自身的不可配置属性,不能被deleteProperty方法删除,否则报错
- ownKeys(target):拦截`Object.getOwnPropertyNames(proxy)`、 `Object.getOwnPropertySymbols(proxy)` `Object.keys(proxy)` ,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性(也就是不会返回目标对象上不存在的属性,属性名为Symbol值,不可遍历的属性)
var obj = {}; Object.defineProperty(obj, 'a', { configurable: false, enumerable: true, value: 10 } ); var p = new Proxy(obj, { ownKeys: function(target) { return ['b']; //因为obj 的a属性是不可配置的,因此这里的返回值必须包含a,否则会报错 } }); Object.getOwnPropertyNames(p) // Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a'
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象或undefined
- defineProperty(target, propKey, propDesc)拦截 `Object.defineProperty(proxy, propKey,propDesc)` 、 `Object.defineProperties(proxy, propDescs) `,返回一个布尔值.如果目标对象不可扩展,则`defineProperty`不能增加目标对象上不存在的属性,否则会报错.此外,如果目标对象的某个属性不可写或不可配置,则defineProperty方法不得改变这两个设置
var handler = { defineProperty (target, key, descriptor) { return false; //返回false,因此对象添加新属性会抛出错误 } }; var target = {}; var proxy = new Proxy(target, handler); proxy.foo = 'bar' // TypeError: proxy defineProperty handler returned false for property '"foo"'
- preventExtensions(target):拦截 `Object.preventExtensions(proxy)` ,返回一个布尔值,否则会被自动转为布尔值
- getPrototypeOf(target):拦截 `Object.getPrototypeOf(proxy)` ,返回一个对象
- isExtensible(target):拦截 `Object.isExtensible(proxy)` ,返回一个布尔值.该方法只能返回布尔值,否则返回值会被自动转为布尔值.这个方法有个强限制,它的返回值必须与目标对象的isExtensible属性保持一致,否则抛出错误
- setPrototypeOf(target, proto):拦截` Object.setPrototypeOf(proxy, proto)` ,返回一个布尔值
- 如果目标对象是函数,那么还有两种额外操作可以拦截
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如 `proxy(...args)` 、` proxy.call(object,...args)` 、 `proxy.apply(...)` 注意这里只要proxy中拦截了apply,call和apply都会被拦截
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如 `new proxy(...args)`.也就是用于拦截new命令,construct方法返回的必须是一个对象,否则会报错
特征
- 如果一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过proxy对象访问该属性会报错
- Proxy.revocable()方法返回一个可取消的Proxy实例
let target={}; let handler={}; let {proxy,revoke}=Proxy.revocable(target,handler); proxy.foo=123; proxy.foo ==>123 revoke(); proxy.foo ==>TypeError:Revoked //Proxy.revocable 方法返回一个对象,该对象的 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例
3. Proxy代理的情况下,目标对象内部的this关键字会指向Proxy代理
const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true 一旦proxy代理target.m,后者内部的this就指向proxy //有些原生对象的内部属性,只有通过正确的this才能拿到,所以Proxy也无法代理这些原生对象的属性 const target = new Date(); const handler = {}; const proxy = new Proxy(target, handler); proxy.getDate(); // TypeError: this is not a Date object. //getDate方法只能在Date对象实例上拿到,如果this不是Date对象实例就会报错