概述
Proxy 实例的方法
Proxy.revocable()
this 问题
实例:Web 服务的客户端
2020.12.13 星期日 17:33
1 概述
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
Proxy 对象的所有用法,都是上面这种形式,不同的只是handler参数的写法。1
2
3
4
5var proxy = new Proxy({}, {
get: function(target, propKey) {
return 35;
}
});
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
如果handler没有设置任何拦截,那就等同于直接通向原对象。
一个技巧是将 Proxy 对象,设置到object.proxy属性,从而可以在object对象上调用。var object = { proxy: new Proxy(target, handler) };
Proxy 实例也可以作为其他对象的原型对象。let obj = Object.create(proxy);
同一个拦截器函数,可以设置拦截多个操作。
对于可以设置、但没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
下面是 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]的操作,返回一个布尔值。
2 Proxy 实例的方法
get()
get方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。
get方法可以继承。
上面代码中,拦截操作定义在Prototype对象上面,所以如果读取obj对象继承的属性时,拦截会生效。
下面的例子使用get拦截,实现数组读取负数的索引。
利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。
下面是一个get方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。
set()
set方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。
上面代码中,由于设置了存值函数set,任何不符合要求的age属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。
利用set方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。
结合get和set方法,就可以做到防止这些内部属性被外部读写。
上面代码中,set方法的第四个参数receiver,指的是原始的操作行为所在的那个对象,一般情况下是proxy实例本身,请看下面的例子。
注意,如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
注意,严格模式下,set代理如果没有返回true,就会报错。
apply()
apply方法拦截函数的调用、call和apply操作。
apply方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。
1 | var target = function () { return 'I am the target'; }; |
has()
has()方法用来拦截HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。
has()方法可以接受两个参数,分别是目标对象、需查询的属性名。
下面的例子使用has()方法隐藏某些属性,不被in运算符发现。
另外,虽然for…in循环也用到了in运算符,但是has()拦截对for…in循环不生效。
construct()
construct()方法用于拦截new命令,下面是拦截对象的写法。
另外,由于construct()拦截的是构造函数,所以它的目标对象必须是函数,否则就会报错。
deleteProperty()
deleteProperty方法用于拦截delete操作,如果这个方法抛出错误或者返回false,当前属性就无法被delete命令删除。
注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。
defineProperty()
defineProperty()方法拦截了Object.defineProperty()操作。
getOwnPropertyDescriptor()
getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
getPrototypeOf() § ⇧
getPrototypeOf()方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。
Object.prototype.proto
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
isExtensible()
isExtensible()方法拦截Object.isExtensible()操作。
ownKeys()
ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for…in循环
for…in循环也受到ownKeys()方法的拦截。
注意,使用Object.keys()方法时,有三类属性会被ownKeys()方法自动过滤,不会返回。
目标对象上不存在的属性
属性名为 Symbol 值
不可遍历(enumerable)的属性
另外,如果目标对象是不可扩展的(non-extensible),这时ownKeys()方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。
preventExtensions()
preventExtensions()方法拦截Object.preventExtensions()。该方法必须返回一个布尔值,否则会被自动转为布尔值。
这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions才能返回true,否则会报错。
setPrototypeOf()
setPrototypeOf()方法主要用来拦截Object.setPrototypeOf()方法。
注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf()方法不得改变目标对象的原型。
3 Proxy.revocable()
Proxy.revocable()方法返回一个可取消的 Proxy 实例。
Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
4 this 问题
在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。下面是一个例子,由于this指向的变化,导致 Proxy 无法代理目标对象。
此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
1 | const target = new Date(); |
另外,Proxy 拦截函数内部的this,指向的是handler对象。
5 实例:Web 服务的客户端
1 | const service = createWebService('http://example.com/data'); |
上面代码新建了一个 Web 服务的接口,这个接口返回各种数据。Proxy 可以拦截这个对象的任意属性,所以不用为每一种数据写一个适配方法,只要写一个 Proxy 拦截就可以了。
同理,Proxy 也可以用来实现数据库的 ORM 层。
# Object.create()
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。 (请打开浏览器控制台以查看运行结果。)Object.create(proto,[propertiesObject])
1 | function MyClass() { |
18:02