Proxy--给对象加一层拦截

463 阅读4分钟

Proxy的相关概念

Proxy用于修改某些操作的默认行为(即对编程语言进行编程,也可称为元编程)

换个角度理解:
把你创建出来的对象比作是大明星,那么Proxy就像是这个大明星的经纪人,
现在有广告商想找这个大明星,那么广告商就得先找经纪人,然后,经纪人觉得,这个广告挺好,
可以接,再去找这个大明星,如果经纪人觉得这个广告商没啥诚意,就可以直接拒绝这个广告商。

proxy充当着这个经纪人的角色,外界对这个对象的访问,都必须先通过这个拦截,因此,也可以对外界的访问进行过滤和改写。

当然,你也会说,广告商会不会不通过经纪人,直接找到这个大明星,这种情况也是存在的。
那么,这种情况下,就没经纪人什么事了,也没有拦截,过滤作用了

即:要使proxy起作用,必须针对proxy实例进行操作,而不是针对目标对象进行操作。(即应该跟经纪人谈,而不是跟大明星谈)

好了,我们现在对proxy有点了解之后,再来看看,怎么使用proxy吧。

在ES6中提供了Proxy构造函数,用来生成Proxy实例。

const proxy = new Proxy(target,handler)
其中:
target就是我们的目标对象(就是上面的例子中的大明星),
handler用来定制拦截行为(也可以理解为给经纪人的工作内容,即让经济人干嘛,做什拦截过滤)。

几个小知识点

tips:
	1:如果handler中没有设置拦截操作,则会直接落在目标对象上,按照原先的方式产生结果
    (就比如,经纪人光拿工资,不干活,把所有事情扔给大明星一样)
    2:当然,指定什么事情,经纪人就做什么,不多也不少(毕竟,经纪人也想下班)
    3:proxy实例也可以做为其他对象的原型对象
    	const proxy = new Proxy({},{
        	get:function(target,propKey){
            	return 35;
            }
        })
        let obj = Object.create(proxy);
        obj.time //35
        这里我们创建一个以proxy为原型的对象,在获取time的时候,obj现在没有这个属性,
        就会在原型链上找,而他的原型又是proxy,在proxy读取这个属性的时候,被拦截了,返回35

13种操作

当然经纪人也不是全能的,不一定会所有的事情,目前,经纪人会做的有13种

1get(target, propKey, receiver):
	拦截对象属性的读取,比如proxy.foo和proxy['foo']2set(target, propKey, value, receiver):
	拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
3has(target, propKey):
	拦截propKey in proxy的操作,返回一个布尔值。
4deleteProperty(target, propKey):
	拦截delete proxy[propKey]的操作,返回一个布尔值。
5ownKeys(target):
	拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、
    Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,
    而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
6getOwnPropertyDescriptor(target, propKey):
	拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
7defineProperty(target, propKey, propDesc):
	拦截Object.defineProperty(proxy, propKey, propDesc)、
    Object.defineProperties(proxy, propDescs),返回一个布尔值。
8preventExtensions(target):
	拦截Object.preventExtensions(proxy),返回一个布尔值。
9getPrototypeOf(target):
	拦截Object.getPrototypeOf(proxy),返回一个对象。
10isExtensible(target):
	拦截Object.isExtensible(proxy),返回一个布尔值。
11setPrototypeOf(target, proto):
	拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
12apply(target, object, args):
	拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
13construct(target, args):
	拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。

更多详情,可以查看:es6.ruanyifeng.com/#docs/proxy

好了,当大明星想炒了这个经纪人了,要怎么做?

Proxy.revocable() ---取消代理

Proxy.revocable()方法返回一个可以取消的Proxy实例

举个例子:
	const target = {};
    const handler = {};
    const {proxy,revoke} = Proxy.revocable(target,handler);
    proxy.foo = 123;
    proxy.foo //123
    revoke();
    proxy.foo // TypeError:Revoke
    
    这里:
    Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke()属性是一个函数
    可以取消Proxy实例。
    所以在执行revoke函数之后,再访问Proxy实例,就会报错。

This指向

虽然Proxy可以代理针对目标对象的访问,但它不是目标对象的透明代理(即不做任何拦截的情况下,也无法保证与目标对象的行为一致)。

主要原因是:目标对象内部的this关键字会指向proxy代理

举个例子:
const target = {
	m:function (){
    	console.log(this === proxy)
    }
}
const handler = {};
const proxy = new Proxy(target,handler)

target.m() //false
proxy.m() // true

这里proxy代理target.m,后者内部的this就是指向proxy,而不是target。

还有一些原生对象的内部属性,只有通过正确的this才能拿到,

所以proxy也无法代理这些原生对象属性,不过可以通过绑定原始对象,解决这个问题。

本文参考:es6.ruanyifeng.com/#docs/proxy