ES6 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] 的操作,返回一个布尔值。
- 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)。
-
get()
get
方法用于拦截某个属性的读取操作,可以接受三个参数,依次为目标对象、属性名和 Proxy 实例本身(严格地说,是操作行为所针对的对象),其中最后一个参数可选。get
方法的用法,上文已经有一个例子,下面是另一个拦截读取操作的例子。get
方法可以继承。上面代码中,拦截操作定义在 Prototype 对象上面,所以如果读取 obj 对象继承的属性时,拦截会生效。下面的例子使用get
拦截,实现数组读取负数的索引。上面代码中,数组的位置参数是 -1,就会输出数组的倒数第一个成员。利用 Proxy,可以将读取属性的操作(get),转变为执行某个函数,从而实现属性的链式操作。上面代码设置 Proxy 以后,达到了将函数名链式使用的效果。下面的例子则是利用 get 拦截,实现一个生成各种 DOM 节点的通用函数 dom。下面是一个 get 方法的第三个参数的例子,它总是指向原始的读操作所在的那个对象,一般情况下就是 Proxy 实例。上面代码中,proxy 对象的 getReceiver 属性是由 proxy 对象提供的,所以 receiver 指向 proxy 对象。上面代码中,d 对象本身没有 a 属性,所以读取 d.a 的时候,会去 d 的原型 proxy 对象找。这时,receiver 就指向 d,代表原始的读操作所在的那个对象。如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则通过 Proxy 对象访问该属性会报错。 -
set()
set
方法用来拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和 Proxy 实例本身,其中最后一个参数可选。假定 Person 对象有一个 age 属性,该属性应该是一个不大于 200 的整数,那么可以使用 Proxy 保证 age 的属性值符合要求。上面代码中,由于设置了存值函数set
,任何不符合要求的 age 属性赋值,都会抛出一个错误,这是数据验证的一种实现方法。利用set
方法,还可以数据绑定,即每当对象发生变化时,会自动更新 DOM。有时,我们会在对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。结合get
和set
方法,就可以做到防止这些内部属性被外部读写。上面代码中,只要读写的属性名的第一个字符是下划线,一律抛错,从而达到禁止读写内部属性的目的。下面是set
方法第四个参数的例子。上面代码中,set
方法的第四个参数 receiver,指的是原始的操作行为所在的那个对象,一般情况下是 proxy 实例本身,请看下面的例子。上面代码中,设置myObj.foo
属性的值时,myObj 并没有foo
属性,因此引擎会到 myObj 的原型链去找foo
属性。myObj 的原型对象 proxy 是一个 Proxy 实例,设置它的foo
属性会触发set
方法。这时,第四个参数receiver
就指向原始赋值行为所在的对象 myObj。注意,如果目标对象自身的某个属性,不可写且不可配置,那么set
方法将不起作用。上面代码中,obj.foo
属性不可写,Proxy 对这个属性的set
代理将不会生效。注意,严格模式下,set
代理如果没有返回 true,就会报错。上面代码中,严格模式下,set
代理返回 false 或者 undefined,都会报错。 -
apply()
apply
方法拦截函数的调用、call
和apply
操作。apply
方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组。下面是一个例子。上面代码中,变量 p 是 Proxy 的实例,当它作为函数调用时(p()
),就会被apply
方法拦截,返回一个字符串。下面是另外一个例子。上面代码中,每当执行 proxy 函数(直接调用或call
和apply
调用),就会被apply
方法拦截。另外,直接调用Reflect.apply
方法,也会被拦截。 -
has()
has
方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in
运算符。has
方法可以接受两个参数,分别是目标对象、需查询的属性名。下面的例子使用has
方法隐藏某些属性,不被in
运算符发现。上面代码中,如果原对象的属性名的第一个字符是下划线,proxy.has
就会返回 false,从而不会被in
运算符发现。如果原对象不可配置或者禁止扩展,这时has
拦截会报错。上面代码中,obj 对象禁止扩展,结果使用has
拦截就会报错。也就是说,如果某个属性不可配置(或者目标对象不可扩展),则has
方法就不得“隐藏”(即返回 false)目标对象的该属性。值得注意的是,has
方法拦截的是 HasProperty 操作,而不是 HasOwnProperty 操作,即has
方法不判断一个属性是对象自身的属性,还是继承的属性。另外,虽然for...in
循环也用到了in
运算符,但是has
拦截对for...in
循环不生效。上面代码中,has
拦截只对in
运算符生效,对for...in
循环不生效,导致不符合要求的属性没有被for...in
循环所排除。 -
construct()
construct
方法用于拦截 new 命令,下面是拦截对象的写法。construct
方法可以接受三个参数。target
:目标对象args
:构造函数的参数对象newTarget
:创造实例对象时,new 命令作用的构造函数(下面例子的p)
construct
方法返回的必须是一个对象,否则会报错。 -
deleteProperty()
deleteProperty
方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除。上面代码中,deleteProperty
方法拦截了 delete 操作符,删除第一个字符为下划线的属性会报错。注意,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty 方法删除,否则报错。
-
defineProperty()
defineProperty
方法拦截了Object.defineProperty
操作。上面代码中,defineProperty
方法返回 false,导致添加新属性总是无效。注意,如果目标对象不可扩展(non-extensible),则 defineProperty 不能增加目标对象上不存在的属性,否则会报错。另外,如果目标对象的某个属性不可写(writable)或不可配置(configurable),则 defineProperty 方法不得改变这两个设置。
-
getOwnPropertyDescriptor()
getOwnPropertyDescriptor
方法拦截Object.getOwnPropertyDescriptor()
,返回一个属性描述对象或者 undefined。上面代码中,handler.getOwnPropertyDescriptor
方法对于第一个字符为下划线的属性名会返回 undefined。 -
getPrototypeOf()
getPrototypeOf
方法主要用来拦截获取对象原型。具体来说,拦截下面这些操作。Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
下面是一个例子。上面代码中,getPrototypeOf
方法拦截Object.getPrototypeOf()
,返回 proto 对象。注意,getPrototypeOf 方法的返回值必须是对象或者 null,否则报错。另外,如果目标对象不可扩展(non-extensible), getPrototypeOf 方法必须返回目标对象的原型对象。
-
isExtensible()
isExtensible
方法拦截Object.isExtensible
操作。上面代码设置了isExtensible
方法,在调用Object.isExtensible
时会输出 called。注意,该方法只能返回布尔值,否则返回值会被自动转为布尔值。这个方法有一个强限制,它的返回值必须与目标对象的isExtensible
属性保持一致,否则就会抛出错误。下面是一个例子。 -
ownKeys()
ownKeys
方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
for...in
循环
下面是拦截Object.keys()
的例子。上面代码拦截了对于target
对象的Object.keys()
操作,只返回a、b、c
三个属性之中的 a 属性。下面的例子是拦截第一个字符为下划线的属性名。注意,使用Object.keys()
方法时,有三类属性会被ownKeys
方法自动过滤,不会返回。- 目标对象上不存在的属性
- 属性名为 Symbol 值
- 不可遍历(enumerable)的属性
上面代码中,ownKeys
方法之中,显式返回不存在的属性(d)、Symbol 值(Symbol.for('secret'))、不可遍历的属性(key),结果都被自动过滤掉。ownKeys
方法还可以拦截Object.getOwnPropertyNames()
。for...in
循环也受到ownKeys
方法的拦截。上面代码中,ownkeys
指定只返回 a 和 b 属性,由于 obj 没有这两个属性,因此for...in
循环不会有任何输出。ownkeys
方法返回的数组成员,只能是字符串或 Symbol 值。如果有其他类型的值,或者返回的根本不是数组,就会报错。上面代码中,ownkeys
方法虽然返回一个数组,但是每一个数组成员都不是字符串或 Symbol 值,因此就报错了。如果目标对象自身包含不可配置的属性,则该属性必须被ownkeys
方法返回,否则报错。上面代码中,obj 对象的 a 属性是不可配置的,这时ownkeys
方法返回的数组之中,必须包含 a,否则会报错。另外,如果目标对象是不可扩展的(non-extensible),这时ownkeys
方法返回的数组之中,必须包含原对象的所有属性,且不能包含多余的属性,否则报错。上面代码中,obj 对象是不可扩展的,这时ownkeys
方法返回的数组之中,包含了 obj 对象的多余属性 b,所以导致了报错。 -
preventExtensions()
preventExtensions
方法拦截Object.preventExtensions()
。该方法必须返回一个布尔值,否则会被自动转为布尔值。这个方法有一个限制,只有目标对象不可扩展时(即Object.isExtensible(proxy)为false),proxy.preventExtensions
才能返回 true,否则会报错。上面代码中,proxy.preventExtensions
方法返回 true,但这时Object.isExtensible(proxy)
会返回 true,因此报错。为了防止出现这个问题,通常要在proxy.preventExtensions
方法里面,调用一次Object.preventExtensions
。 -
setPrototypeOf()
setPrototypeOf
方法主要用来拦截Object.setPrototypeOf
方法。下面是一个例子。上面代码中,只要修改target
的原型对象,就会报错。注意,该方法只能返回布尔值,否则会被自动转为布尔值。另外,如果目标对象不可扩展(non-extensible),setPrototypeOf 方法不得改变目标对象的原型。
-