proxy翻译过来的意思就是”代理“,ES6对Proxy的定位就是target对象(原对象)的基础上通过handler增加一层”拦截“,返回一个新的代理对象,之后所有在Proxy中被拦截的属性,都可以定制化一些新的流程在上面,先看一个最简单的例子
const target = {}; // 要被代理的原对象 // 用于描述代理过程的handler const handler = { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } } // obj就是一个被新的代理对象 const obj = new Proxy(target, handler); obj.a = 1 // setting a! console.log(obj.a) // getting a!
上面的例子中我们在target对象上架设了一层handler,其中拦截了针对target的get和set,然后我们就可以在get和set中间做一些额外的操作了
注意1:对Proxy对象的赋值操作也会影响到原对象target,同时对target的操作也会影响Proxy,不过直接操作原对象的话不会触发拦截的内容~
obj.a = 1; // setting a! console.log(target.a) // 1 不会打印 "getting a!"
注意2:如果handler中没有任何拦截上的处理,那么对代理对象的操作会直接通向原对象
const target = {}; const handler = {}; const obj = new Proxy(target, handler); obj.a = 1; console.log(target.a) // 1
既然proxy也是一个对象,那么它就可以做为原型对象,所以我们把obj的原型指向到proxy上后,发现对obj的操作会找到原型上的代理对象,如果obj自己有a属性,则不会触发proxy上的get,这个应该很好理解
const target = {}; const obj = {}; const handler = { get: function(target, key){ console.log(`get ${key} from ${JSON.stringify(target)}`); return Reflect.get(target, key); } } const proxy = new Proxy(target, handler); Object.setPrototypeOf(obj, proxy); proxy.a = 1; obj.b = 1 console.log(obj.a) // get a from {"a": 1} 1 console.log(obj.b) // 1
ES6的Proxy实现了对哪些属性的拦截?
通过上面的例子了解了Proxy的原理后,我们来看下ES6目前实现了哪些属性的拦截,以及他们分别可以做什么? 下面是 Proxy 支持的拦截操作一览,一共 13 种
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);
以上是目前es6支持的proxy,具体的用法不做赘述,有兴趣的可以到去研究每种的具体用法,其实思想都是一样的,只是每种对应了一些不同的功能~
实际场景中 Proxy 可以做什么?
实现私有变量
js的语法中没有private这个关键字来修饰私有变量,所以基本上所有的class的属性都是可以被访问的,但是在有些场景下我们需要使用到私有变量,现在业界的一些做法都是使用”_变量名“来”约定“这是一个私有变量,但是如果哪天被别人从外部改掉的话,我们还是没有办法阻止的,然而,当Proxy出现后,我们可以用代理来处理这种场景,看代码:
const obj = { _name: 'nanjin', age: 19, getName: () => { return this._name; }, setName: (newName) => { this._name = newName; } } const proxyObj = obj => new Proxy(obj, { get: (target, key) => { if(key.startsWith('_')){ throw new Error(`${key} is private key, please use get${key}`) } return Reflect.get(target, key); }, set: (target, key, newVal) => { if(key.startsWith('_')){ throw new Error(`${key} is private key, please use set${key}`) } return Reflect.set(target, key, newVal); } }) const newObj = proxyObj(obj); console.log(newObj._name) // Uncaught Error: _name is private key, please use get_name newObj._name = 'newname'; // Uncaught Error: _name is private key, please use set_name console.log(newObj.age) // 19 console.log(newObj.getName()) // nanjin