Proxy类 & Reflect对象

165 阅读4分钟

Proxy类 & Reflect对象

Proxy 类

  • 是一个类,如果我们希望监听一个对象的相关操作,可以先创建一个代理对象(Proxy 对象)
  • 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们对原对象进行哪些操作
  • 可以将代理理解为通用化的settergetter,区别是 每个settergetter仅能控制单个对象属性,而代理可用于对象交互的通 注意 用处理,包括调用对象的方法。

基本用法

Proxy 是一个类,可以传入两个参数 new Proxy(target, handler)

  • target:需要被代理的对象
  • handler:里面可以自定义捕获器trap,一共有13种(包括get / set
  • Proxy 返回的是一个代理对象,可以通过这个对象访问、配置被代理对象的属性(会经过捕获器的处理
 const obj = {
   name: "xmc",
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, key) {
     return target[key];
   },
   set(target, key, newValue) {
     target[key] = newValue;
   },
 });
 ​
 console.log(objProxy.name); // 'xmc'
 objProxy.name = "zs";
 console.log(obj.name); // 'zs'

get / set

常用的捕获器就那几个,既然是响应式基础,我们就看看实现响应式的两个主要捕获器

它们都会传入三个参数target / property / receiverset 会多传一个newValue,用于修改被代理对象的值

  • target:指向被代理的对象
  • property :需要修改的属性名
  • receiver:用于正确的在捕获器中传递上下文。大多数场景指代理对象本身,如果有实例继承了代理对象并在原型链上调用了这个get,那么就会指向这个子实例。
 const obj = {
   name: "xmc",
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, property, receiver) {
     console.log(target === obj);        // true
     console.log(receiver === objProxy); // true
   },
   set(target, property, newValue, receiver) {
     console.log(target === obj);        // true
     console.log(receiver === objProxy); // true
   },
 });
 ​
 // 仅使用代理对象
 console.log(objProxy.name);
 objProxy.name = "aaa";
 const obj = {
   name: "xmc",
 };
 ​
 const son = {};
 ​
 const objProxy = new Proxy(obj, {
   get(target, key, receiver) {
     console.log("objProxy:", receiver === objProxy);
     console.log("son:", receiver === son);
   },
 });
 ​
 // objProxy: true
 // son: false
 console.log(objProxy.name);
 ​
 // 使用子实例
 son.__proto__ = objProxy;
 // objProxy: false
 // son: true
 console.log(son.name);

Reflect 对象

Reflect 提供了和 Proxy 的捕获器是一一对应的方法。

这些方法有点像 Object 中操作对象的方法。比如 Reflect.getPrototypeOf(target) 类似于 Object.getPrototypeOf()

我们有Object可以做这些操作,为什么还需要有 Reflect 这样的新增对象呢?

  • 在早期ES规范中,没有考虑到对象本身操作如何设计会更加规范,所以就将这些API放到了Object上面
  • 但是由于Object作为一个构造函数,直接拿来做操作是不合适的
  • 另外还包含类似于 in、delete 操作符,让JS看起来是会有一些奇怪的

所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect上面

Receiver

Reflect

在捕获器 get / set 中可以传入 receiver 参数,它有什么用呢?

这个参数类似于apply / call 中添加的对象,能够改变 target 内部 this 的指向

来看个例子

 const obj = {
   _name: "xmc",
   get name() {
     return this._name;
   }
 };
 ​
 const res1 = Reflect.get(obj, "name");
 const res2 = Reflect.get(obj, "name", { _name: "zs" });
 ​
 console.log(res1, res2); // xmc zs

Proxy

Proxy 的一些捕获器中,也有 receiver ,但是这里指向的是正确的上下文对象

 const obj = {
   name: "xmc",
 };
 const son = {}
 ​
 const objProxy = new Proxy(obj, {
   get(target, key, receiver) {
     console.log(objProxy === receiver);
   }
 });
 ​
 objProxy.name;  // true
 ​
 son.__proto__ = objProxy;
 son.name; // false

应用场景

场景一

假如我们在对象中存在外部不能直接访问的属性,但是我们又需要监听对象中所有属性,如下

 const obj = {
   name: "xmc",
   _age: 21,
   get age() {
     return this._age;
   },
 };

显然,如果我们在代理的对象捕获器 getreturn target[key] ,那么this._age 会直接被调用,而不会通过 Proxy 的代理

 const obj = {
   name: "xmc",
   _age: 21,
   get age() {
     return this._age;
   },
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, key) {
     console.log(key);
     return target[key];
   },
 });
 ​
 console.log(objProxy.age); // age 21

这里的 get 只会被调用一次,且 keyage

如果我们使用 receiver 又会是什么样呢

 const obj = {
   name: "xmc",
   _age: 21,
   get age() {
     return this._age;
   },
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, key, receiver) {
     console.log(key);
     return Reflect.get(target, key, receiver);
   },
 });
 ​
 console.log(objProxy.age);  // age _age 21

这里的 get 会被调用两次,obj._age 成功通过代理被访问

场景二

如果有实例继承了代理对象,如果调用的方法来自于代理对象,且方法内部有使用 this,简单的在getreturn Relect(target, key) ,这样不能将正确的上下文传入。

 const obj = {
   name: "xmc",
   get value() {
     return this.name;
   },
 };
 ​
 const son = {
   name: "zs",
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, key, receiver) {
     return Reflect.get(target, key);
   },
 });
 ​
 son.__proto__ = objProxy;
 ​
 console.log(objProxy.value);// xmc
 console.log(son.value);     // xmc (希望是zs)

所以需要使用 receiver,即传入当前正确的上下文

 const obj = {
   name: "xmc",
   get value() {
     return this.name;
   },
 };
 ​
 const son = {
   name: "zs",
 };
 ​
 const objProxy = new Proxy(obj, {
   get(target, key, receiver) {
     return Reflect.get(target, key, receiver);  // 传入 receiver
   },
 });
 ​
 son.__proto__ = objProxy;
 ​
 console.log(objProxy.value);// xmc
 console.log(son.value);     // zs

参考资料

《JavaScript高级程序设计》(第四版)

《JavaScript忍者秘籍》(第二版)

为什么Proxy一定要配合Reflect使用?