第十一章 代理与反射

155 阅读3分钟

Reflect

Reflect是一个内置的JS对象,它提供了一系列方法,可以让开发者通过调用这些方法,访问一些JS底层的功能

Reflect API

  1. Reflect.set(target, propertyKey, value)

    设置target对象的属性propertyKey的值为value

  2. Reflect.get(target,propertyKey)

    读取target对象的属性propertyKey

  3. Reflect.apply(target, thisArgument, argumentsArr)

    调用一个函数target,并绑定this和参数列表

  4. Reflect.deleteProperty(target, propertyKey)

    删除target对象的属性propertyKey

  5. Reflect.defineProperty(target, propertyKey, attributes)

    类似于Object.defineProperty(),不过Object.defineProperty()若属性描述符配置不当会报错(如同时配置getter和value),而Reflect.defineProperty()若配置不当不会报错,而只是会返回false

  6. Reflect.construct(target, argumentsArr)

    利用构造函数target创建一个对象

  7. Reflect.has(target, propertyKey)

    判断target对象中是否有属性propertyKey

其它Reflect API:developer.mozilla.org/zh-CN/docs/…

Proxy

proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性访问、赋值、枚举、函数调用等)

创建Proxy

 const proxy = new Proxy(target, handler);

target为接受代理的对象,handler也是一个对象,其中可以配置许多动作捕获的方法,即捕获器

为target添加了proxy后,就不应该再直接操作target,而是需要通过操作proxy来间接操作target

handler中的动作捕获器

Reflect中有的API,handler中都可以有,且名称和参数列表也相同

handler中的捕获器函数,它们的第一个参数target就是接受代理的对象target

  • has(target, propertyKey)

    in操作符的动作捕获器

    当对proxy使用in操作符时,会调用该捕获器,且该函数的返回值会作为其in表达式的返回值

     const target = {
         a: 1
     };
     ​
     const proxy = new Proxy(target, {
         has(target, propertyKey){
             console.log(target, propertyKey);
             return "a" in target;
         }
     });
     ​
     console.log("a" in proxy);      // 将调用handler的has捕获器
     // { a: 1 } "a"
     // true
    
  • get(target, propertyKey)

    属性读取操作的动作捕获器

    当读取proxy的属性时,会调用该捕获器,且该函数的返回值会作为获取到的值

  • set(target, propertyKey, value)

    属性设置操作的动作捕获器

    当设置proxy的属性时,会调用该捕获器

  • deleteProperty(target, propertyKey)

    delete操作符的动作捕获器

    当删除proxy的属性时,会调用该捕获器

  • apply(target, thisArgument, argumentsArr)

    函数调用操作的动作捕获器

    当调用proxy时,会调用该捕获器,且该函数的返回值会作为调用proxy的值

     function target(){
         console.log("target fn");
     }
     ​
     const proxy = new Proxy(target, {
         apply(target, thisArgument, argumentsArr){
             console.log("apply");
             target();
         }
     });
     ​
     proxy();
     // "apply"
     // "target fn"
    
  • construct(target, argumentsArr)

    构造函数调用操作的动作捕获器

    当使用new关键字调用proxy时,会调用该捕获器,且该函数的返回值会作为new proxy()所创建的对象

注意:handler中所有的捕获器都是可选的,如果没有定义某个捕或器,那么在对proxy进行相应操作时,会自动转换为对target的对应操作

Proxy mdn

观察者模式

可以通过观察目标对象的属性变化,来进行一些其他的操作

使用Object.defineProperty实现观察者模式

 function observer(target) {
     for (const prop in target) {
         let val = target[prop];
         if(typeof target[prop] === "object" && typeof target[prop] !== "null"){
             observer(target[prop]);
         }
         Object.defineProperty(target, prop, {
             get() {
                 // 这里可以进行一些其它操作
                 return val;
             },
             set(value) {
                 // 这里可以进行一些其它操作
                 val = value;
             }
         });
     }
 }

缺陷:

  1. 需要深度遍历target,会造成效率上的损失
  2. 对于设置了观察者后再往target中新增的属性,将不具有被观察的效果
  3. 无法观察到除设置和获取外的其他操作

使用Proxy实现观察者模式

 function observer(target) {
     const proxy = new Proxy(target, {
         get(target,propertyKey){
             // 这里可以进行一些其它操作
             return Reflect.get(target, propertyKey);
         },
         set(target, propertyKey, value){
             // 这里可以进行一些其它操作
             Reflect.set(target, propertyKey, value);
         },
         has(target, propertyKey){
             // 这里可以进行一些其它操作
             return Reflect.has(target, propertyKey);
         },
         delete(target, propertyKey){
             // 这里可以进行一些其它操作
             Reflect.delete(target, propertyKey);
         },
         ...
     });
     return proxy;
 }