Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”
(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”
,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
下面是 Proxy 支持的拦截操作一览,一共 13 种:
(1)get(target, propKey, receiver)
拦截对象属性的读取,比如proxy.foo和proxy['foo']。
最后一个参数receiver
是一个对象,可选,参见下面Reflect.get的部分。
(2)set(target, propKey, value, receiver)
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
(3)has(target, propKey)
拦截propKey in proxy的操作,以及对象的hasOwnProperty方法,返回一个布尔值。
(4)deleteProperty(target, propKey)
拦截delete proxy[propKey]的操作,返回一个布尔值。
(5)apply(target, object, args)
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
(6)construct(target, args)
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
(7)ownKeys(target)
该方法返回目标对象所有自身的属性的属性名。
(8)getOwnPropertyDescriptor(target, propKey)
返回属性的描述对象
(9)defineProperty(target, propKey, propDesc)
返回一个布尔值
(10)
preventExtensions(target)
拦截Object.preventExtensions(proxy)
,返回一个布尔值。
(11)getPrototypeOf(target)
拦截Object.getPrototypeOf(proxy)
,返回一个对象。
(12)isExtensible(target)
拦截Object.isExtensible(proxy)
,返回一个布尔值
(13)setPrototypeOf(target, proto)
拦截Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截
get()
get
方法用于拦截某个属性的读取操作
。上文已经有一个例子,下面是另一个拦截读取操作的例子。
var person = {
name: "jack"
};
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 // "jack"
proxy.age // 抛出一个错误
上面代码表示,如果访问目标对象不存在
的属性,会抛出一个错误。如果没有这个拦截函数,访问不存在的属性,只会返回undefined
。
set()
set
方法用来拦截某个属性的赋值操作
。可以接受四个参数
,依次为目标对象、属性名、属性值和 Proxy 实例本身
,其中最后一个参数可选。
let obj = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('错误信息:不是整数');
}
if (value > 200) {
throw new RangeError('错误信息:年龄大于200');
}
}
// 对于age以外的属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, obj);
person.age = 13;
person.age // 13
person.age = 'jack' // age不是整数报错
person.age = 300 // age大于200报错
上面代码中,由于设置了存值函数set
,任何不符合要求的age属性
赋值都会抛出一个错误。
apply()
apply
方法拦截函数的调用
、call
和apply
操作。
apply
方法可以接受三个参数,分别是目标对象
、目标对象的上下文对象
(this)和目标对象的参数数组
。
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (num) {
return num + 1 ;
};
var proxy = new Proxy(sum, twice); // 第一个参数相当于原值,第二个参数相当于过滤器,变量proxy相当于过滤后的值
proxy(2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
上面代码中,每当执行proxy函数
(直接调用或call
和apply
调用),就会被apply
方法拦截。直接调用Reflect.apply
方法也会被拦截。
has()
has
方法用来拦截HasProperty
操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。
has
方法可以接受两个参数,分别是目标对象
、需查询的属性名
。
下面的例子使用has
方法隐藏某些属性,不被in
运算符发现。
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log( '_prop' in proxy ) // false
上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has
就会返回false,从而不会被in
运算符发现。
construct()
construct
方法用于拦截new
命令。
construct
方法可以接受三个参数
。
target:
目标对象args:
构建函数的参数对象newTarget
:创造实例对象时,new
命令作用的构造函数(下面例子的p
)var p = new Proxy(function() {}, {
construct: function(target, args) {
console.log('called: ' + args.join(', '));
return { value: args[0] * 10 };
}
});
new p(1).value
// "called: 1"
// 10
construct
方法返回的必须是一个对象
,否则会报错。
deleteProperty()
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error('删除下划线开头的报错');
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop // 报错内容:删除下划线开头的报错
上面代码中,deleteProperty
方法拦截了delete
操作符,删除第一个字符为下划线的属性会报错。