概述 Proxy 对象是定义在全局对象上的一个对象的构造函数,通过对目标对象的一些基本操作进行重定义并生成新的代理对象。说起来比较绕,看如下示例:
var obj = new Proxy ({}, { set (target, key, val ) { console .log('call setter' ); return Reflect .set(target, key, val); } }); obj.val = 3 ;
Proxy 对象生成的新对象修改了 {}
对象的赋值方式,使用 obj.val
赋值,比原始行为多了一行打印日志。
使用 Proxy 对象生成代理对象的语法如下:
var proxyObj = new Proxy(target, handler);
其中 handler
是拥有一系列需要更改的默认行为的方法的函数的集合,支持更改的行为和 Reflect 对象的静态函数一一对应:
apply: 拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
construct: 拦截 new
命令,比如 new proxy(...args)
defineProperty: 拦截 Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值
deleteProperty: 拦截 delete proxy[propKey]
的操作,返回一个布尔值
get: 拦截对象属性的读取,比如 proxy.foo
和 proxy['foo']
。
getOwnPropertyDescriptor: 拦截 Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象
getPrototypeOf: 拦截 Object.getPrototypeOf(proxy)
、 Object.prototype.__proto__
、 Object.prototype.isPrototypeOf()
、 Object.getPrototypeOf()
、 Reflect.getPrototypeOf()
、 instanceof
运算符 ,返回一个对象
has: 拦截 propKey in proxy
的操作,返回一个布尔值
isExtensible: 拦截 Object.isExtensible(proxy)
ownKeys: 拦截 Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性
preventExtensions: 拦截 Object.preventExtensions(proxy)
,返回一个布尔值
set: 拦截对象属性的设置,比如 proxy.foo = v
或 proxy['foo'] = v
setPrototypeOf: 拦截 Object.setPrototypeOf(proxy, proto)
,返回一个布尔值
Proxy 支持修改的这些行为和 Reflect 中的行为意义一致,不再一一说明。
应用场景 Proxy 顾名思义,类似于设计模式中的代理模式,通常有一下几种用途:
取消代理 对于某些场合下,可能会需要收回对代理的使用,这种情况下可以使用 Proxy.revocable
,该方法返回一个对象,对象包含两个属性,一个属性是 proxy
实例,另一个是取消 proxy
实例的方法:
var {proxy, revoke} = Proxy .revocable({}, {});proxy.value = 1 ; proxy.value; revoke(); proxy.value;
访问控制和管理 在 js 语法中并不存在私有属性,我们可以使用 Proxy 阻止属性被访问:
var obj = { _private : '_private' , public : 'public' } var proxyObj = new Proxy (obj, { get (target, key ) { if (key.indexOf('_' ) > -1 ) { throw Error ("私有属性,禁止访问" ); } return Reflect .get(target, key); } }); proxyObj.public proxyObj._private
通过修改 obj 的 getter,将所有的下划线 _ 属性的访问拦截,并抛出异常,其他的属性正常返回值。
在设置私有属性的时候最好把私有属性遍历和 setter 以及删除也禁用了:
var obj = { _private : '_private' , public : 'public' } var proxyObj = new Proxy (obj, { get (target, propKey ) { if (propKey.indexOf('_' ) > -1 ) { throw Error ("私有属性,禁止访问" ); } return Reflect .get(target, propKey); }, set (target, propKey ) { if (propKey.indexOf('_' ) > -1 ) { throw Error ("私有属性,禁止访问" ); } return Reflect .get(target, propKey); }, deleteProperty (target, propKey ) { if (propKey.indexOf('_' ) > -1 ) { throw Error ("私有属性,禁止删除" ); } return Reflect .deleteProperty(target, propKey); }, ownerKeys (target ) { return Reflect .ownerKeys(target).filter(item => item[0 ] !== '_' ); } });
在进行类库编写的时候经常会有接口过期,为了过渡,通常我们不会删除该方法,但是最好能够给出警告信息:
var obj = { oldFunc : () => {}, newFunc : () => {} } var proxy = new Proxy (obj, { get (target, key ) { if (key == 'oldFunc' ) { console .warn('警告! oldFunc 已过时,请使用 newFunc 代替。' ); } return Reflect .get(target, key); } }) proxy.oldFunc();
同样我们也可以进行方法拦截,过滤,记录日志等操作,操作和上面类似,不再详细说明。
校验 通过 Proxy 可以很方便的实现属性校验:
var validatorCreater = (target, validator ) => new Proxy (target, { _validator : validator, set (target, key, value, receiver ) { if (this ._validator[key]) { if (!this ._validator[key](value)) { throw Error (`${value} is not a valid value` ); } return Reflect .set(target, key, value, receiver); } throw Error (`${key} is not a valid property` ); } }); var validators = { name (val ) { return typeof val === 'string' ; }, age (val ) { return typeof val === 'number' && val > 0 ; } } var person = validatorCreater({}, validators);person.name = 1 ; person.name = '1' ; person.age = '1' ; person.age = -1 ; person.age = 12 ;
数据变化监听 当数据发生改变时,能够监听到这种变化,这是数据绑定的实现基础,来看下面这个例子:
实现 onChange(obj, callback)
函数,当 obj 变化时(新增、删除、修改、查找),调用 callback
函数
function onChange (objToWatch, onChangeFunction ) { if (typeof objToWatch !== "object" ) { throw TypeError ("objToWatch must be a object" ); } return new Proxy (objToWatch, { get (target, propKey, receiver ) { onChangeFunction(); const result = Reflect .get(target, propKey, receiver); if (typeof result === "object" ) { return onChange(result, onChangeFunction); } return result; }, set (target, propKey, value, receiver ) { onChangeFunction(); return Reflect .set(target, propKey, value, receiver); }, deleteProperty (target, propKey ) { onChangeFunction(); delete target[propKey]; return true ; } }); } let counter = 0 ;const logger = () => { counter++; }; const obj = { a : { b : { c : { d : "123" } } } };const proxy = onChange(obj, logger);console .log(proxy.a); console .log(counter); console .log(proxy.a.b.c.d); console .log(counter); proxy.a = "b" ; console .log(counter); delete proxy.a; console .log(counter);
Decorator Proxy
和 Decorator
在某些情况下还是挺像的,使用上可以区分为:Proxy
的核心作用是控制外界对被代理者内部的访问,Decorator
的核心作用是增强被装饰者的功能。有些功能的实现可以用Proxy
也可以使用 Decorator
。
参考