Proxy-Reflect使用
Proxy
查看
需要:如果存在一个对象,我们需要去监听这个对象中的属性被设置或获取的过程,应该如何操作?
在ES6之前,我们可以通过Object.defineProperty()
的存取属性描述符来对属性的操作进行监听。
1 | const obj = { name: 'obj', age: 18 } |
但这样是存在缺点的:
-
Object.defineProperty设计的初衷并不是去为了监听对象的所有属性的,我们在定义某些属性的时候,初衷只是为了定义普通的属性,只是因为其存在访问器这种特点,就强行用属性描述符去操作了。
-
Object.defineProperty只能监听属性的存取,但如果我们想监听更加丰富的操作,比如新增、删除属性等,那么Object.defineProperty是无能为力的。
ES6中,新增了Proxy类,用于帮助我们创建一个代理对象,从而对对象操作进行监听。
Proxy语法
const p = new Proxy(target, handler)
target:要创建代理的对象
handler: 是定义了代理的自定义行为的对象
如果我们想要侦听某些具体的操作,那么可以在handler中添加对应的捕获器。
对于上面的案例,通过Proxy实现为:
1 | const obj = { name: 'obj', age: 18 } |
Proxy所有捕获器

这里说一下construct捕获器和apply捕获器,这两个捕获器是应用于函数对象的。
1 | function foo() { |
handler.construct(target, argumentsList, newTarget)
参数:
target:被代理的函数
argumentsList:创建实例时传入的参数
newTarget:最初被调用的构造函数,上述为proxy对象apply(target, thisArg, argumentsList)
apply时调用函数的捕获器,包括普通调用,apply/call调用参数:
target:被代理的函数
thisArg:调用函数时传入的this
argumentsList:调用函数时传入的参数
Reflect
查看
Reflet是ES6新增的一个API,它提供了许多操作对象的方法,Object的方法基本都可以通过Reflect实现,用法和Object相同,但是Reflect的方法更简洁,并且更符合语义。
比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf(),Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty()。
那么我们可能会有一些疑惑,既然Reflect可以做的,Object都可以做,为什么还要有Reflect呢?
-
由于在早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计更加规范,所以将相应API都放在了Object上面。
-
但是Object的API设计存在一些问题,一些是静态方法(如
Object.keys()、Object.defineProperty()
),一些又是实例方法obj.hasOwnProperty
,这样调用方法不一致的问题,增大了学习者的心理负担。 -
Object的API的错误处理不统一,某些方法如
Object.defineProperty()
是通过抛出异常处理错误,而某些方法是给出返回值,如Object.freeze()
当传入一个原始值时,如传入一个数字,并不会抛出错误,而是将其转换为对应的包装类,然后将其返回。 -
对this绑定存在隐患,Object的实例方法依赖this绑定,若方法被错误调用,如提取后单独使用,会导致意外行为。
-
对于Object的实例方法易被重写。
-
…
总的来说,对于Object的API并没有进行统一的规范,操作混乱,容易出错。
Reflect的出现可以说是Object的操作增强。
-
统一的操作函数,Reflect将所有对象的操作静态化,解决了Object的一会静态,一会实例方法的API设计规范问题。
-
合理返回值设计,对于不合法的调用,Reflect会返回true/false,解决了Object的返回值不统一的问题。
-
填补了空白功能,如Object要获取对象的键是,需要通过
obj.getOwnPropertyNames()
和obj.getOwnPropertySymbols()
,但Reflect提供了Reflect.ownKeys
是上述两种方法的组合。
Reflect的常用方法:

Proxy和Reflect结合使用
查看
上文我们说了Reflect和Object的区别,Reflect的出现标准化了对象的底层操作(提供一致的方法接口)和弥补了Object方法的不足(如错误处理和函数式风格)。
但Reflect还有一个应用场景,就是与Proxy结合使用(支持Proxy的陷阱(捕获器)实现)。
举个例子:
1 | const obj = { |
以上代码get捕获器只触发了一次,即只触发了对name属性的获取,但按理来说,对此对象的所有属性的获取都应该触发get捕获器,但只触发获取name的访问器,这是由于this指向的问题,我们在get捕获器中通过操作源对象的方式获取属性值,导致this并没有指向代理对象,这破坏了代理的封装性。
在看一个代码:
1 | const obj = { |
以上代码set捕获器只触发了一次,和上述的get捕获器一样,都是捕获器中直接操作源对象的结果,破坏了代理的封装性。
其实Proxy的get/set捕获器中还有一个receiver
参数,receiver指向当前代理对象,而Reflect的get/set方法也可以接受一个receiver参数,通过receiver可以保证proxy的陷阱实现的正确性。
所以可以对以上代码进行修改,将Proxy和Reflect结合使用,如下:
1 | const obj = { |
补充:
在ES5我们实现继承时有借用构造函数思想,即改变this指向,实现对父类构造函数的调用。
Reflect提供了相同操作的方法:Reflect.construct(target, argumentsList, newTarget)
参数:
target: 借用构造函数的对象
argumentsList: 构造函数的参数
newTarget: 需要创建实例的构造方法/类
关于Proxy和Reflect结合的思考
查看
上文我们已经了解到,Proxy的是对象的代理,通过对其自定义行为对象中添加相应的捕获器,可以监听到对象相应的的操作,Reflect实现了对对象操作的规范化,弥补了对象的不足,同时将Proxy和Reflect结合使用,可以保证Proxy的陷阱的实现正确性,保证了Proxy的代理封装性。
Proxy与被代理对象的数据是同步的,因为Proxy最终读取始终是被代理对象的属性,即使不通过Proxy对源对象进行修改。
Reflect操作源对象的get/set方法是对对象的内部方法[[Get]]
/[[Set]]
的显示封装,而通过obj.[key]
则是对其的隐式封装,但对其的操作容易受this的影响而变为对其他对象的操作,而Reflect可以通过receiver保证this指向的正确性,放如Proxy中,既可以保证this永远指向receiver,从而实现对源对象的[[Get]]
/[[Set]]
操作都会走Proxy进而触发捕获器。
即当不存在访问器属性时,this其实与[[Get]]
/[[Set]]
的操作并无关系,从Reflect.get(target, key, receiver)
中可以看出,对属性值的获取其值只需target和key就行,通过target的内部方法[[Get]]
即可获取到属性值,而不需要this,当存在访问器时,通过receiver只是提供正确的this指向代理对象,同时也可以看出Proxy的操作一直就是对target(被代理对象)的操作,而不是对Proxy本身的操作,所以默认情况下,Proxy保持与被代理对象的数据同步,但如果Proxy在捕获器中进行了自定义操作(未对对象进行相应操作),代理对象与被代理对象就不一定数据同步了。