ES6 Proxy 代理器
-
定义和使用
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为。这里暂时先不解释具体的语法,只看运行结果。对设置了拦截行为的对象 obj,去读写它的属性,就会得到下面的结果。上面代码说明,Proxy 实际上重载(overload)了点运算符,即用自己的定义覆盖了语言的原始定义。ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。Proxy 对象的所有用法,都是上面这种形式,不同的只是handler
参数的写法。其中,new Proxy()
表示生成一个 Proxy 实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。下面是另一个拦截读取属性行为的例子。上面代码中,作为构造函数,Proxy 接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有 Proxy 的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个get
方法,用来拦截对目标对象属性的访问请求。get
方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回 35,所以访问任何属性都得到 35。注意,要使得 Proxy 起作用,必须针对 Proxy 实例(上例是 proxy 对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler
没有设置任何拦截,那就等同于直接通向原对象。上面代码中,handler
是一个空对象,没有任何拦截效果,访问 Proxy 就等同于访问target
。一个技巧是将 Proxy 对象,设置到 object.proxy 属性,从而可以在 object 对象上调用。Proxy 实例也可以作为其他对象的原型对象。上面代码中,Proxy 对象是 obj 对象的原型,obj 对象本身并没有 time 属性,所以根据原型链,会在 Proxy 对象上读取该属性,导致被拦截。同一个拦截器函数,可以设置拦截多个操作。对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 -
Proxy.revocable()
Proxy.revocable
方法返回一个可取消的 Proxy 实例。Proxy.revocable
方法返回一个对象,该对象的 Proxy 属性是 Proxy 实例,revoke
属性是一个函数,可以取消 Proxy 实例。上面代码中,当执行revoke
函数之后,再访问 Proxy 实例,就会抛出一个错误。Proxy.revocable
的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。 -
this 问题
虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理。上面代码中,一旦 Proxy 代理target.m
,后者内部的 this 就是指向 Proxy,而不是target
。下面是一个例子,由于 this 指向的变化,导致 Proxy 无法代理目标对象。上面代码中,目标对象 jane 的 name 属性,实际保存在外部 WeakMap 对象_name
上面,通过 this 键区分。由于通过proxy.name
访问时,this 指向 proxy,导致无法取到值,所以返回 undefined。此外,有些原生对象的内部属性,只有通过正确的 this 才能拿到,所以 Proxy 也无法代理这些原生对象的属性。上面代码中,getDate
方法只能在 Date 对象实例上面拿到,如果 this 不是 Date 对象实例就会报错。这时,this 绑定原始对象,就可以解决这个问题。 -
Web 服务的客户端
Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。同理,Proxy 也可以用来实现数据库的 ORM 层。 -