Proxy是一个很有趣的对象,它能够修改某些操作的默认行为,等同于在语言层面做出修改,属于一种‘元编程’,即对编程语言进行编程。
Proxy其实很好理解,就是在目标对象之前架设一层拦截,外界的访问都得通过这层拦截,所以我们可以实现对外界访问的过滤和改写。
Proxy的使用其实很简单,举几个栗子你就清楚了:
我们重新定义属性的读取(get)和设置(set)行为,当我读取Person对象的age属性时,当age属性值大于100时,就让它等于99:
var person = { name:‘fancy‘, age:123 }//定义一个对象 var proxy = new Proxy(person,//第一个参数传要代理的对象 {//第二个参数传要重定义的属性 get:(target,key)=>{ if (key == ‘age‘&& target[key] > 100) { return 99 }else{ return target[key] } } }); proxy.name //fancy proxy.age //99 person.age //123
我们创建了一个Proxy对象去代理person对象,并将属性的get方法重定义,当属性值等于‘age’并且值大于100时,返回99,否则直接返回属性值。get接收两个参数,第一个是原对象,第二个是属性名。
数据打印出来后,发现proxy的age值变成了99,而person的age值仍然是123,proxy实际上是重载(overload)了点运算符,用自己的定义覆盖了语言的原始定义。
我们再用set
方法用来拦截某个属性的赋值操作。
假定Person
对象有一个age
属性,该属性应该是一个不大于200的整数,那么可以使用Proxy
保证age
的属性值符合要求。
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‘ // 抛出异常:The age is not an integer person.age = 300 // 抛出异常:The age seems invalid
set方法也常常用来做数据绑定,当对象发生改变时,自动更新视图;还可以用来禁止读写内部属性等等。
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)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(...)
。
(13)construct(target, args)
拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)
。
网上有很多实例可以参考,这里就不一一列举了,有兴趣自己写框架的朋友,可以深究一下Proxy的实现原理。