Proxy
Proxy 用于修改某些操作的默认行为。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”
生成实例
//第一个参数target表示所要拦截的目标对象,第二个参数handler是一个配置对象,用来定制拦截行为
var proxy = new Proxy(target, handler);
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
//解析:第一个参数是空对象,表示拦截的目标对象是空对象;第二个参数对象里有个get方法,该函数将拦截对应的操作,这里用来拦截对目标对象属性的访问请求
//于是每次访问属性都会返回35
proxy.time // 35
proxy.name // 35
proxy.title // 35
Proxy要点
-
只有针对
proxy
对象进行操作,拦截器才起作用 -
如果
handler
没有设置任何拦截,那就等同于直接通向原对象var target = {}; //声明一个需要被拦截的目标对象 var handler = {}; //声明一个拦截行为,这里为空 var proxy = new Proxy(target, handler); //实例化一个拦截 proxy.a = 'b'; //由于proxy没有设置拦截,那么这里的proxy就相当于target target.a // "b"
-
同一个拦截器函数,可以设置拦截多个操作
Proxy技巧
-
将 Proxy 对象,设置到
object.proxy
属性var object = { proxy: new Proxy(target, handler) };
-
将 Proxy 对象作为其他对象的原型对象
var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); let obj = Object.create(proxy); //将Proxy对象作为obj的原型对象 obj.time // 35
Proxy 支持的拦截操作
1. get(target, propKey, receiver)
拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`,`receiver`是个对象,可选
2. set(target, propKey, value, receiver)
拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值
3. has(target, propKey)
拦截`propKey in proxy`的操作,返回一个布尔值
4. deleteProperty(target, propKey)
拦截`delete proxy[propKey]`的操作,返回一个布尔值
5. ownKeys(target)
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
6. getOwnPropertyDescriptor(target, propKey)
拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
7. defineProperty(target, propKey, propDesc)
拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值
8. preventExtensions(target)
拦截Object.preventExtensions(proxy),返回一个布尔值
9. getPrototypeOf(target)
拦截Object.getPrototypeOf(proxy),返回一个对象
10. isExtensible(target)
拦截Object.isExtensible(proxy),返回一个布尔值
11. setPrototypeOf(target, proto)
拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
12. apply(target, object, args)
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。三个参数分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组
13. construct(target, args)
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
Proxy 实例的方法
-
get
var person = { name: "张三" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { //先判断属性是否在该对象中 return target[property]; //在的话就返回该属性值 } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); //否则抛出一个错误 } } }); proxy.name // "张三" proxy.age // 抛出一个错误
get方法可以继承
let proto = new Proxy({}, { get(target, propertyKey, receiver) { console.log('GET '+propertyKey); return target[propertyKey]; } }); let obj = Object.create(proto); obj.xxx // "GET xxx"
-
set()
let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } // 对于age以外的属性,直接保存 obj[prop] = value; } }; let person = new Proxy({}, validator); person.age = 100; person.age // 100 person.age = 'young' // 报错 person.age = 300 // 报错
结合
get
和set
方法,就可以做到防止这些内部属性被外部读写。比如设置的属性名如果是下划线开头,表示这些属性不应该被外部使用var handler = { get (target, key) { invariant(key, 'get'); //给该验证方法传属性 return target[key]; }, set (target, key, value) { invariant(key, 'set'); //给该验证方法传属性 target[key] = value; return true; } }; function invariant (key, action) { if (key[0] === '_') { //判断传过来的属性第一个字符是否为_,有则抛出错误 throw new Error(`Invalid attempt to ${action} private "${key}" property`); } } var target = {}; var proxy = new Proxy(target, handler); proxy._prop // Error: Invalid attempt to get private "_prop" property proxy._prop = 'c' // Error: Invalid attempt to set private "_prop" property
-
apply()
var target = function () { return 'I am the target'; }; var handler = { apply: function () { return 'I am the proxy'; } }; var p = new Proxy(target, handler); p() // "I am the proxy"