Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
var obj = new Proxy({}, {
get: function (target, propKey, receiver) {
console.log(`getting ${propKey}!`);
return Reflect.get(target, propKey, receiver);
},
set: function (target, propKey, value, receiver) {
console.log(`setting ${propKey}!`);
return Reflect.set(target, propKey, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
- proxy13种拦截操作
get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo
和proxy['foo']
。
set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。
has(target, propKey):拦截propKey in proxy
的操作,返回一个布尔值。
deleteProperty(target, propKey):拦截delete proxy[propKey]
的操作,返回一个布尔值。
ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而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)
,返回一个布尔值。
getPrototypeOf(target):拦截Object.getPrototypeOf(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)
。
一个拦截器设置多种拦截:
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(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
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 // 报错
由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
apply
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 3
上面代码中,每当执行proxy函数(直接调用或call和apply调用),就会被apply方法拦截。
另外,直接调用Reflect.apply方法,也会被拦截。
Reflect.apply(proxy, null, [9, 10]) // 38
construct
construct()方法返回的必须是一个对象,否则会报错。由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。 |
const p = new Proxy({}, {
construct: function(target, argumentsList) {
return {};
}
});
new p() // 报错
// Uncaught TypeError: p is not a constructor
注意,construct()方法中的this指向的是handler,而不是实例对象。
const handler = {
construct: function(target, args) {
console.log(this === handler);
return new target(...args);
}
}
let p = new Proxy(function () {}, handler);
new p() // true