- Proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
1 | //ES6生成Proxy实例 |
要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作;如果handler没有设置任何拦截,那就等同于直接通向原对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
//技巧:将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用
var object = { proxy: new Proxy(target, handler) };
//Proxy实例也可以作为其他对象的原型对象
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
//可以进行多操作拦截
var handler = {
get: function(target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function(target, thisBinding, args) {
return args[0];
},
construct: function(target, args) {
return {value: args[1]};
}
};
var fproxy = new Proxy(function(x, y) {
return x + y;
}, handler);
fproxy(2,20);//apply ==> 2
new fproxy(1,11);//construct ==> {value: 11}
fproxy.hello;//get ==> Hello, helloProxy支持的拦截操作如下
- get(target, propKey, receiver)拦截对象属性的读取,比如proxy.foo和proxy[‘foo’],返回类型不限。最后一个参数receiver可选,当target对象设置了propKey属性的get函数时,receiver对象会绑定get函数的this对象。
- set(target, propKey, value, receiver)拦截对象属性的设置,比如proxy.foo = v或proxy[‘foo’] = v,返回一个布尔值。
- has(target, propKey)拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。
- deleteProperty(target, propKey)拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target)拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy),返回一个数组。该方法返回对象所有自身的属性,而Object.keys()仅返回对象可遍历的属性。
- getOwnPropertyDescriptor(target, propKey)拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
- defineProperty(target, propKey, propDesc)拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target)拦截Object.preventExtensions(proxy),返回一个布尔值。
- isExtensible(target)拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto)拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。
- apply(target, object, args)拦截Proxy实例作为函数调用的操作,比如proxy(…args)、proxy.call(object, …args)、proxy.apply(…)。
- construct(target, args)拦截Proxy实例作为构造函数调用的操作,比如new proxy(…args)。
各拦截操作实战用途如下
- get 可以在读取不存在的属性时抛出一个错误,而不是仅仅返回无意义的undefined;可以在返回之前做逻辑处理,比如实现数组负数索引取值;可以创建链式调用。
- set 可以用来实施更新DOM
- has 可以对in运算符做特殊处理,如不让带大写A的属性被in发现;has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断一个属性是对象自身的属性,还是继承的属性。for…in操作内部也是用到HasProperty操作,所以has方法在for…in循环时也会生效。
construct方法返回的必须是一个对象,否则会报错。
1
2
3
4
5
6
7
8
9
10
11//在返回真正的对象前可以加以处理,但返回值必须是对象
var p = new Proxy(function() {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 30 };
}
});
new p(10).value
// "called: 10"
// 300getPrototypeOf用来拦截object.getPrototypeOf()运算符,以及Reflect.getPrototypeOf,instanceof运算符,Object.prototype.isPrototypeOf
- Proxy.revocable(),Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。
1
2
3
4
5
6
7
8let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo //123
revoke();
proxy.foo // TypeError : Revoke