美文网首页
35.Proxy和Reflect详解

35.Proxy和Reflect详解

作者: 静昕妈妈芦培培 | 来源:发表于2021-11-29 09:36 被阅读0次

    监听对对象的操作

    • 有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
    • 下面实现的弊端:
    • 如果我们想监听对对象更加丰富的操作,比如新增属性,删除属性,那么Object.defineProperty是无能为力的
    使用Object.defineProperty()监听对对象进行操作
    const obj = {
      name: "coderwhy",
      age: 18,
    };
    
    Object.keys(obj).forEach((key) => {
      let value = obj[key];
      Object.defineProperty(obj, key, {
        set: function (newVal) {
          value = newVal;
          console.log(`监听到给${key}属性设置值`);
        },
        get: function () {
          console.log(`监听到获取${key}属性的值`);
          return value;
        },
      });
    });
    
    console.log(obj.name);
    obj.age = 30;
    
    // 监听到获取name属性的值
    // coderwhy
    // 监听到给age属性设置值
    
    

    Proxy基本使用

    在ES6中,新增了一个Proxy类,用于帮助我们创建一个代理:

    • 如果我们希望监听对一个对象的相关操作,我们可以先创建一个这个对象的代理对象(Proxy对象)
    • 之后对改对象的所有操作,都通过代理对象完成,
    • 代理对象可以监听到我们想要对原对象进行哪些操作
    new Proxy(target, handler)
    

    target为要监听的目标对象
    Proxy对象有13种默认的捕获器方法,
    如果需要监听对原对象的某种操作,然后做出相应处理,可以在handler对象中重写对应的捕获器方法

    使用Proxy监听对对象进行操作
    const obj = {
      name: "coderwhy",
      age: 18,
    };
    
    //创建一个obj的代理对象
    const objProxy = new Proxy(obj, {
      //重写proxy对象的get捕获器
      get(target, key) {
        console.log(`监听到获取obj对象的${key}属性的值`);
        return target[key];
      },
      //重写proxy对象的set捕获器
      set(target, key, newVal) {
        console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
        target[key] = newVal;
      },
    });
    
    //对obj对象要做的所有操作,都对其代理对象操作,
    console.log(objProxy.name);
    objProxy.age = 30;
    
    // 监听到获取obj对象的name属性的值
    // coderwhy
    // 监听到obj对象的age属性的值被重新赋值了
    
    

    Proxy的13中捕获器用法

    const obj = {
      name: "coderwhy",
      age: 18,
    };
    
    //创建一个obj的代理对象
    const objProxy = new Proxy(obj, {
      //重写proxy对象的get捕获器:监听获取目标对象的属性值
      get(target, key, receiver) {
        //get捕获器有三个参数:
        //target为目标对象,key为当前操作的属性名,receiver为目标对象的代理对象即objProxy
        console.log(`监听到获取obj对象的${key}属性的值`);
        return target[key];
      },
      //重写proxy对象的set捕获器:监听设置目标对象的属性值
      set(target, key, newVal, receiver) {
        //set捕获器有四个参数:
        //target为目标对象,key为当前操作的属性名,newVal为给此属性设置的新值,receiver为目标对象的代理对象即objProxy
        console.log(`监听到obj对象的${key}属性的值被重新赋值了`);
        target[key] = newVal;
      },
      //监听对对象进行的in操作
      has: function (target, key) {
        console.log(`监听到对obj对象的${key}属性的in操作`);
        return key in target;
      },
      //监听对对象的delete操作的捕获器
      deleteProperty: function (target, key) {
        console.log(`监听到对obj对象的${key}属性的delete操作`);
        delete target[key];
      },
    });
    
    //对obj对象要做的所有操作,都对其代理对象操作,
    
    //获取对象的属性值
    console.log(objProxy.name);
    //设置对象的属性值
    objProxy.age = 30;
    
    // in操作
    console.log("name" in objProxy);
    
    //delete操作
    delete objProxy.age;
    console.log(obj);
    
    

    proxy对函数对象的监听

    function foo(name, age) {
      this.name = name;
      this.age = age;
    }
    
    //创建foo的代理对象
    const fooProxy = new Proxy(foo, {
      //监听对函数对象的apply调用
      apply(target, thisArg, argArray) {
        //target为目标对象,thisArg为给目标函数绑定的this,argArray为传给目标函数的参数数组
        console.log("对foo进行了apply调用");
        return target.apply(thisArg, argArray);
      },
      //监听对函数对象的new调用
      construct(target, argArray) {
        //target为目标对象,argArray为传给目标函数的参数数组
        console.log("对foo进行了new调用");
        return new target(...argArray);
      },
    });
    
    fooProxy.apply({}, ["why", "18"]);
    new fooProxy("lily", 30);
    
    

    另外七种捕获器用法:
    下面为知识点扩展:

    • Object.getPrototypeOf(target)

      • target 目标对象
      • 获取目标对象的proto原型对象
    • Object.setPrototypeOf(target, prototype)

      • target 目标对象
      • 设置目标对象的proto原型对象为prototype
    • Object.getOwnPropertyDescriptor(target, prop)

      • 返回指定对象上一个自有属性对应的属性描述符。
      • target 目标对象
      • prop 目标属性
    • Reflect.ownKeys(target) 方法

      • 返回一个由目标对象自身的属性键组成的数组。
      • 它的返回值等同Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。
    • Object.isExtensible(target) 方法

      • 判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。
    • Object.preventExtensions(target)方法

      • 让一个对象变的不可扩展,也就是永远不能再添加新的属性。
    const obj = { name: "why", age: 18 };
    const objProxy = new Proxy(obj, {
      //监听对目标对象进行的Object.getPrototypeOf操作
      getPrototypeOf(target) {
        console.log("监听到了对obj进行的Object.getPrototypeOf操作");
        return Object.getPrototypeOf(target);
      },
      setPrototypeOf(target, prototype) {
        // target为目标对象 prototype为要被设置为目标对象原型对象的对象
        console.log("监听到了对obj进行的Object.setPrototypeOf操作");
        return Object.setPrototypeOf(target, prototype);
      },
      getOwnPropertyDescriptor(target, prop) {
        console.log("监听到了对obj进行的Object.getOwnPropertyDescriptor操作");
        return Object.getOwnPropertyDescriptor(target, prop);
      },
      defineProperty(target, property, descriptor) {
        console.log("监听到了对obj进行的Object.defineProperty操作");
        return Object.defineProperty(target, property, descriptor);
      },
      //监听对目标对象进行的Object.getOwnPropertySymbols或Object.getOwnPropertyNames或Reflect.ownKeys操作
      ownKeys(target) {
        console.log(
          "监听到了对obj进行的Object.getOwnPropertyNames/Object.getOwnPropertySymbols操作"
        );
        return Object.getOwnPropertyNames(target);
      },
      isExtensible(target) {
        console.log("监听到了对obj进行的Object.isExtensible操作");
        return Object.isExtensible(target);
      },
      preventExtensions(target) {
        console.log("监听到了对obj进行的Object.preventExtensions操作");
        return Object.preventExtensions(target);
      },
    });
    const objPrototype = Object.getPrototypeOf(objProxy);
    console.log(objPrototype); // [Object: null prototype] {}
    
    Object.setPrototypeOf(objProxy, { title: "讲师" });
    Object.getOwnPropertyDescriptor(objProxy, "name");
    Object.defineProperty(objProxy, "height", {
      value: "1.88",
      writable: true,
      enumerable: true,
      configurable: true,
    });
    Object.getOwnPropertyNames(objProxy);
    Object.getOwnPropertySymbols(objProxy);
    console.log(Reflect.ownKeys(objProxy));
    console.log(Object.isExtensible(objProxy));
    Object.preventExtensions(objProxy);
    console.log(Object.isExtensible(objProxy));
    
    

    知识拓展:
    例:

    const s = Symbol();
    const obj = {
      name: "why",
      age: 30,
      [s]: "我是灵魂画手",
    };
    
    console.log(Reflect.ownKeys(obj)); //[ 'name', 'age', Symbol() ]
    console.log(Object.getOwnPropertyNames(obj)); //[ 'name', 'age' ]
    console.log(Object.getOwnPropertySymbols(obj)); //[ Symbol() ]
    console.log(
      Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))
    ); //[ 'name', 'age', Symbol() ]
    

    Reflect

    Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射。
    那么这个Reflect有什么用呢?

    • 它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;
    • 比如Reflect.getPrototypeOf(target)类似于 Object.getPrototypeOf();
    • 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty() ;

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

    • 这是因为在早期的ECMA规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些API放到了Object上面;
    • 但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;
    • 另外还包含一些类似于 in、delete操作符,让JS看起来是会有一些奇怪的;
    • 所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;

    那么Object和Reflect对象之间的API关系,可以参考MDN文档:
    https://developer.mozilla.org/zh�CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods
    下表详细介绍了Object 和 Reflect API上可用方法之间的差异。请注意,如果API中不存在某种方法,则将其标记为N/A。

    image.png
    image.png
    image.png
    let s = Symbol();
    const obj = {
      _name: "why",
      get name() {
        return this._name;
      },
      set name(newVal) {
        this._name = newVal;
      },
      s: "不可描述的秘密",
    };
    
    //为对象obj添加一个不可枚举属性work
    Object.defineProperty(obj, "work", {
      value: "teacher",
    });
    
    // console.log(Object.getOwnPropertyDescriptors(obj))
    console.log(Object.keys(obj)); // [ '_name', 'name', 's' ]
    //Object.keys(target)返回目标对象所有可枚举属性名组成的数组
    console.log(Reflect.ownKeys(obj)); //[ '_name', 'name', 's', 'work' ]
    //Reflect.ownKeys(target)返回目标对象素有的属性名组成的数组
    

    Refect和Proxy结合使用

    const obj = {
      name: "why",
      age: 18,
    };
    
    const objProxy = new Proxy(obj, {
      get: function (target, key) {
        console.log(`监听到获取对象的${key}的属性值`)
        return Reflect.get(target, key);
      },
      set: function (target, key, newVal) {
        //Reflect.set的执行返回一个布尔值,如果设置值成功,为true,如果失败为false
        const result = Reflect.set(target, key, newVal);
        if (result) {
          console.log("设置值成功");
        } else {
          console.log("设置值失败");
        }
      },
    });
    
    objProxy.name = 'lily'
    console.log(objProxy.name)
    

    Reflect的receiver参数

    const obj = {
      _name: "why",
      get name() {
        return this._name;
      },
      set name(newVal) {
        this._name = newVal;
      },
    };
    
    const objProxy = new Proxy(obj, {
      get(target, key) {
        console.log(`监听到了获取对象的${key}的值的操作`)
        return Reflect.get(target, key)
      }
    })
    
    console.log(objProxy.name)
    
    image.png
    分析:
    • objProxy.name 的执行会触发Proxy对象的get捕获器,执行Relect.get(target,key)
    • Relect.get(target,key)的执行 会执行对象的name属性的存取属性描述符的get方法,从而执行this._name,
    • this._name中的this为obj,则是通过obj._name获取_name属性的值,
    • 而没有通过代理对象获取_name属性的值==》objProxy._name,所以不会再触发Proxy的get捕获器
    • 而实际上,我们希望捕获每一次获取对象属性的值的操作,
    • 那么需要把对象的name属性的属性描述符的get方法中的this改为Proxy对象objProxy

    Reflect.get(target, key, receiver)方法接受三个参数,

    • target: 操作的目标对象
    • key: 操作的属性
    • receiver: 如果target对象中指定了getter,receiver则为getter调用时的this值。

    所以可以 通过给Reflect.get传递receiver来改变目标对象中getter调用时的this指向
    而Proxy对象的get捕获器接收的第三个参数receiver为该Proxy对象

    const obj = {
      _name: "why",
      get name() {
        return this._name;
      },
      set name(newVal) {
        this._name = newVal;
      },
    };
    
    const objProxy = new Proxy(obj, {
      get(target, key, receiver) {
        console.log(`监听到了获取对象的${key}的值的操作`)
        return Reflect.get(target, key, receiver)
      }
    })
    
    console.log(objProxy.name)
    
    image.png

    Reflect.construct(target, argArray[, newTarget])

    • Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...argArray).
    • target
      被运行的目标构造函数
    • argArray
      类数组,目标构造函数调用时的参数。
    • newTarget 可选
      作为新创建对象的原型对象的constructor属性, 参考 new.target 操作符,默认值为target
    • 返回值
      以target(如果newTarget存在,则为newTarget)函数为构造函数,argumentList为其初始化参数的对象实例。
    function Student(name, age) {
      this.name = name;
      this.age = age;
    }
    function Teacher() {}
    const stu = new Student("lily", 18);
    console.log(stu); //Student { name: 'lily', age: 18 } 可以看到创建了一个Student类的对象
    console.log(stu.__proto__ === Student.prototype); //true
    
    //现在有一个需求,执行Student方法,但是创建出来的是Teacher类的对象
    var stu1 = Object.create(Teacher.prototype); //创建一个空对象,其__proto__指向Teacher.prototype
    Student.apply(stu1, ["lily", 18]);
    console.log(stu1); // Teacher { name: 'lily', age: 18 }
    console.log(stu1.__proto__ === Teacher.prototype); //true
    
    //下面的方式与上面的方式等效
    const stu2 = Reflect.construct(Student, ["lily", 18], Teacher);
    console.log(stu2); // Teacher { name: 'lily', age: 18 }
    console.log(stu2.__proto__ === Teacher.prototype); //true
    
    

    非常感谢王红元老师的深入JavaScript高级语法让我学习到很多 JavaScript 的知识

    相关文章

      网友评论

          本文标题:35.Proxy和Reflect详解

          本文链接:https://www.haomeiwen.com/subject/ydqxxrtx.html