ES6中新增了Proxy对象,从字面量上看可以理解为代理器,主要用于改变对象的默认访问行为,实际表现是在访问对象之间增加了一层拦截,任何对对象的访问行为都会通过这层拦截。在拦截中,我们可以增加自定义的行为。
Proxy的基本语法:
1 | let proxy = new Proxy(target, handler) |
target —— 是要包装的对象,可以是任何东西,包括函数。
handler —— 代理配置:带有“捕捉器”(“traps”,即拦截操作的方法)的对象。比如 get 捕捉器用于读取 target 的属性,set 捕捉器用于写入 target 的属性,等等。
对 proxy 进行操作,如果在 handler 中存在相应的捕捉器,则它将运行,并且 Proxy 有机会对其进行处理,否则将直接对 target 进行处理。
Proxy实例
1 | //我们用 get 来实现一个对象的默认值。 |
上面实例中,我们定义了一个包含get()函数的配置对象,表示的是对代理对象的属性进行读取操作,就会触发get()函数。因此在执行numbersProxy[1],即访问Proxy实例数组项为1的值,会触发get()函数,在控制台输出自定义的结果。
使用Proxy时的注意事项
必须通过代理实例访问
如果需要配置对象的拦截行为生效,那么必须是对代理实例的属性进行访问,而不是直接对目标对象进行访问。
Proxy实例函数及其基本使用
在上面例子中,我们通过访问代理对象的属性来触发自定义配置对象的get()函数。而get()函数只是Proxy实例支持的总共13中函数中的一种,这13中函数汇总如下。
get(target,property,receiver)
用于拦截对象的读取属性操作。例如调用proxy.name或者proxy[name],其中target表示的是目标对象,property表示的是读取的属性值,receiver表示的是配置对象(最初被调用的对象。通常是 proxy 本身)。
set(target,property,value,receiver)
拦截对象属性的写入操作,即设置属性值,例如proxy.name = ‘xx’或者proxy[name] = ‘xx’,其中target表示目标对象,property表示的是将要设置的属性,value表示将要设置的属性值,receiver表示的是配置对象。
has(target,prop)
拦截hasProperty的操作,返回一个布尔值,最典型的表现形式是执行prop in target,其中target表示目标对象,prop表示判断的属性值。
deleteProperty(target,property)
拦截delete proxy[property]的操作,返回一个布尔值,表示是否执行成功,其中target表示目标对象,property表示将要删除的属性。
ownkeys(target)
拦截Object.getOwnPropertyNames(target)、Object.getOwnPropertySymbols(target)、Object.keys(target)、for…in循环等操作,其中target表示的是获取对象自身所有的属性名。
拓展 ,
Object.getOwnPropertyNames(obj) 返回非 Symbol 键。
Object.getOwnPropertySymbols(obj) 返回 Symbol 键。
Object.keys/values() 返回带有 enumerable 标志的非 Symbol 键/值。
for..in 循环遍历所有带有 enumerable 标志的非 Symbol 键,以及原型对象的键。
getOwnPropertyDecriptor(target,prop)
拦截Object.getOwnPropertyDecriptor(proxy, prop)操作,返回属性的属性描述符构成的对象,其中target表示目标对象,prop表示需要获取属性描述符集合的属性。
defineProperty(target,property,descriptor)
拦截Object.defineProperty(proxy, property, decriptor)、Object.defineProperties(proxy, decriptors)操作,返回一个布尔值,其中target表示目标对象,property表示新增的属性,descriptor表示的是属性描述符对象。
preventExtensions(target)
拦截Object.preventExtensions(proxy)操作,返回一个布尔值,表示的是让一个对象变得不可扩展,不能再增加新属性,其中target表示目标对象。
isExtensible(target)
拦截Object.isExtensible(proxy),返回一个布尔值,表示对象是否可拓展。其中target表示目标对象。
getPrototypeOf(target)
拦截Object.getPrototypeOf(proxy)操作,返回一个对象,表示的是拦截获取对象原型属性,其中target表示目标对象。
setPropertyOf(target,prototype)
拦截Object.setPrototype(proxy, prototype)操作,返回一个布尔值,表示的是拦截设置对象的原型属性的行为,其中target表示目标对象,prototype表示新的原型对象。
apply(target,object,args)
拦截Proxy实例作为函数调用的操作,例如proxy(…args)、proxy.call(object, …args)、proxy.apply(…),其中target表示目标对象,object表示函数的调用方,args表示函数调用传递的参数。
constructor(target,args)
拦截Proxy实例作为构造函数调用的操作,例如new Proxy(…args),其中target表示目标对象,args表示函数调用传递的参数。
这些函数都有一个通用的特性,即如果再target中使用了this关键字,再通过Proxy处理后,this关键之指向的是Proxy实例,而不是目标对象target。
1 | const person = { |
Proxy应用场景
读取不存在属性
通常,尝试读取不存在的属性会返回 undefined。
创建一个代理,在尝试读取不存在的属性时,该代理抛出一个错误。
这可以帮助及早发现编程错误。
1 | let user = { |
读取负索引的值
数组的索引值时从0开始依次递增的,正常情况下我们无法读取负索引的值,但是通过Proxy的get()函数可以做到这一点。
负索引实际就是从数组的尾部元素开始,从后往前,寻找元素的位置。
换句话说,array[-N] 与 array[array.length - N] 相同。
1 | let arr = [1, 2, 3, 4, 5] |
禁止访问私有属性
在一些约定熟成的写法中,私有属性都会以下划线(_)开头,事实上,我们并不希望用户能访问到私有属性。
我们将需要以下捕捉器:
get 读取此类属性时抛出错误
set 写入属性时抛出错误
deleteProperty 删除属性时抛出错误
has 在使用 in 方法时排除以 _ 开头的属性
ownKeys 在使用 for..in 和像 Object.keys 这样的的方法时排除以 _ 开头的属性
1 | let user = { |
Proxy访问属性的限制
当我们期望使用Proxy对对象的属性进行代理,并修改属性的返回值时,我们需要这个属性不能同时为不可配置和不可写。如果这个属性同时为不可配置和不可写,那么在通过代理读取属性时,会抛出异常。
1 | let target = Object.defineProperties({}, { |
拦截属性赋值操作
定义一个person对象,包含一个age属性,取值在0~100之间,只要设置的值不在区间内,就会抛出异常。
1 | let user = { |
函数的拦截
Proxy中提供了apply()函数,用于拦截函数调用的操作,函数调用包括直接调用、call()函数调用、apply()函数调用3
种方式。
通过对函数调用的拦截,可以加入自定义操作,从而得到新的函数处理结果。
1 | function sum (num1, num2) { |