• js高级程序设计-代理与反射


    代理基础

    代理可以提供拦截目标对象并向基本操作中嵌入额外行为的能力.目标对象可以直接被操作,也可以通过代理来操作

    1.创建空代理

    代理是使用 Proxy 构造函数创建的。这个构造函数接收两个参数:目标对象和处理程序对象.

    const target = {
      id: "target",
    };
    const handler = {};
    const proxy = new Proxy(target, handler);
    
    // 访问id会访问同一个值
    console.log(target.id); //target
    console.log(proxy.id); //target
    
    // 给目标属性赋值会反应在目标对象和代理对象上
    target.id = "foo";
    console.log(target.id); //foo
    console.log(proxy.id); //foo
    
    // 给代理对象赋值也会反映在两个对象上
    proxy.id = "bar";
    console.log(target.id); //bar
    console.log(proxy.id); //bar
    
    // hasOwnProperty()方法在两个地方都会应用到目标对象上
    console.log(target.hasOwnProperty("id")); //true
    console.log(proxy.hasOwnProperty("id")); //true
    
    // Proxy的原型对象是undefined
    // 因此不能使用instanceof操作符
    console.log(Proxy.prototype); //undefined
    console.log(target instanceof Proxy); //TypeError: Function has non-object prototype 'undefined' in instanceof check
    console.log(proxy instanceof Proxy); //TypeError: Function has non-object prototype 'undefined' in instanceof check
    
    // 严格相等可以区分代理和目标对象
    console.log(proxy === target); //false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    2.定义捕获器(trap)

    使用代理的主要目的是可以定义捕获器(trap)。捕获器就是在处理程序对象中定义的“基本操作的
    拦截器”.每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对
    象之前先调用捕获器函数,从而拦截并修改相应的行为。

    const target = {
      foo: "bar",
    };
    const handler = {
      // get捕获器,当代理对象执行get操作时,即访问通过代理对象访问目标对象属性时会触发该捕获器
      get() {
        return "handler override";
      },
    };
    const proxy = new Proxy(target, handler);
    
    console.log(target.foo); //bar
    console.log(proxy.foo); //handler override
    
    console.log(target["foo"]); //bar
    console.log(proxy["foo"]); //handler override
    
    console.log(Object.create(target)["foo"]); //bar
    console.log(Object.create(proxy)["foo"]); //handler override
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.捕获器参数和反射 API

    所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为。

    const target = {
      foo: "bar",
    };
    
    const handler = {
      // 参数依次是 目标对象,要访问的属性名,代理对象
      get(trapTarget, property, receiver) {
        console.log(trapTarget === target);
        console.log(property);
        console.log(receiver === proxy);
      },
    };
    
    const proxy = new Proxy(target, handler);
    
    proxy.foo;
    // 访问代理对象proxy.foo,触发get拦截器,依次打印
    // true
    // foo
    // true
    // 有了这些参数就可以重建原始操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    事实上,开发者不需要手动重建原始行为,可通过全局的 Reflect 对象(封装了原始行为)的同名方法来重建,处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为.因此可以定义没有任何额外行为的空代理.

    const target = {
      foo: "bar",
    };
    
    const handler = {
      get: Reflect.get,
    };
    
    const proxy = new Proxy(target, handler);
    
    console.log(target.foo); //bar
    console.log(proxy.foo); //bar
    
    // 创建捕获所有方法的代理,直接使用Reflect作为处理程序对象
    const proxy2 = new Proxy(target, Reflect);
    console.log(target.foo); //bar
    console.log(proxy2.foo); //bar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.捕获器不变式

    捕获处理程序的行为必须遵循“捕获器不变式”。捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为。如目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 TypeError.

    const target = {};
    // foo属性不可写且不可配置
    Object.defineProperty(target, "foo", {
      configurable: false,
      writable: false,
      value: "bar",
    });
    
    // get捕获器返回不是原来的value,那么访问proxy.foo会报错
    const handler = {
      get() {
        return "qux";
      },
    };
    
    const proxy = new Proxy(target, handler);
    proxy.foo; //TypeError
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.可撤销的代理

    中断代理对象与目标对象之间的联系.Proxy 也暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联。

    const target = {
      foo: "bar",
    };
    
    const handler = {
      get() {
        return "intercepted";
      },
    };
    
    const { proxy, revoke } = Proxy.revocable(target, handler);
    console.log(proxy.foo); //intercepted
    console.log(target.foo); // bar
    
    revoke(); // 调用revoke函数
    console.log(proxy.foo); //TypeError: Cannot perform 'get' on a proxy that has been revoked
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    6.使用反射 API

    某些情况下应该优先使用反射 API,这是有一些理由的

    6.1 反射 API 与对象 API

    注意

    1. 反射 API 并不限于捕获处理程序
    2. 大多数反射 API 在 Object 类型上有对应的方法
      通常 Object 上的方法用于通用程序,反射方法用于细粒度的对象控制和操作

    6.2 状态标记

    很多反射方法返回称作“状态标记”的布尔值,表示意图执行的操作是否成功.
    以下反射方法都会返回状态标记:

    • Reflect.defineProperty()
    • Reflect.preventExtensions()
    • Reflect.setPrototypeOf()
    • Reflect.set()
    • Reflect.deleteProperty()
    const o = {};
    if (Reflect.defineProperty(o, "foo", { value: "bar" })) {
      console.log("success");
    } else {
      console.log("failure");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    6.3 用一等函数替代操作符

    以下反射方法提供只有通过操作符才能完成的操作:

    • Reflect.get():可以代替对象属性访问符.
    • Reflect.set():可以代替=赋值操作符
    • Reflect.has():可以代替 in 操作符
    • Reflect.deleteProperty():可以代替 delete 操作符
    • Reflect.construct():可以代理 new 操作符

    7.代理另一个代理

    创建一个代理另一个代理的代理

    const target = {
      foo: "bar",
    };
    const firstProxy = new Proxy(target, {
      get() {
        console.log("第一个代理");
        return Reflect.get(...arguments);
      },
    });
    
    const secondProxy = new Proxy(firstProxy, {
      get() {
        console.log("第二个代理");
        return Reflect.get(...arguments);
      },
    });
    
    console.log(secondProxy.foo);
    // 第二个代理
    // 第一个代理
    // bar
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    8. 代理的问题与不足

    8.1 代理中的 this 问题

    如果目标对象某些方法依赖对象实例,那么代理该目标对象会出错,要代理其构造函数可以解决

    代理捕获器和反射方法

    代理可以捕获 13 种不同的基本操作,有各自不同的反射 API 方法,参数,关联 ECMAScript 操作和不变式,在代理上执行的任何一种操作只会有一个捕获处理程序被调用,不存在重复捕获的情况

    1. get()

    get()捕获器在获取属性值的操作被调用,对应反射 API-Reflect.get()

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @param {*} receiver 代理对象或继承代理对象的对象
       * @returns
       */
      get(target, property, receiver) {
        console.log("get()");
        return Reflect.get(...arguments);
      },
    });
    
    // 拦截的操作
    proxy.foo; // get()
    proxy["foo"]; // get()
    Object.create(proxy)["foo"]; // get()
    Reflect.get(proxy, "foo"); // get()
    
    // 返回值无限制
    
    // 捕获不变式
    // 如果target.property 不可写且不可配置,则处理程序返回的值必须与target.property 匹配。
    // 如果target.property 不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    2. set()

    set()捕获器会在设置属性值的操作中被调用。对应的反射 API 方法为 Reflect.set()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @param {*} value 要赋给属性的值
       * @param {*} receiver 代理对象或继承代理对象的对象
       * @returns // 返回true表示成功,false表示失败
       */
      set(target, property, value, receiver) {
        console.log("set()");
        return Reflect.set(...arguments);
      },
    });
    
    // 拦截的操作
    proxy.foo = "bar"; // set()
    proxy["foo"] = "bar"; // set()
    Object.create(proxy)["foo"] = "bar"; // set()
    Reflect.set(proxy, "foo", "bar"); // set()
    
    // 捕获不变式
    // 如果target.property 不可写且不可配置,则不能修改目标属性的值。
    // 如果target.property 不可配置且[[Set]]特性为undefined,则不能修改目标属性的值。
    // 在严格模式下,处理程序中返回false 会抛出TypeError。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    3. has()

    has()捕获器会在 in 操作符中被调用。对应的反射 API 方法为 Reflect.has()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @returns has()必须返回布尔值,表示属性是否存在
       */
      has(target, property) {
        console.log("has()");
        return Reflect.has(...arguments);
      },
    });
    
    // 拦截的操作
    "foo" in proxy; //has()
    "foo" in Object.create(proxy); //has()
    Reflect.has(proxy, "foo"); //has()
    
    // 捕获不变式
    // 如果target.property 存在且不可配置,则处理程序必须返回true。
    // 如果target.property 存在且目标对象不可扩展,则处理程序必须返回true。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4. defineProperty()

    defineProperty()捕获器会在 Object.defineProperty()中被调> 用。对应的反射 API 方法为
    Reflect.defineProperty()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       * 
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @param {*} descriptor 包含可选的enumerable、configurable、writable、value、get 和set
    定义的对象
       * @returns defineProperty()必须返回布尔值,表示属性是否成功定义
       */
      defineProperty(target, property, descriptor) {
        console.log("defineProperty()");
        return Reflect.defineProperty(...arguments);
      },
    });
    
    // 拦截的操作
    Object.defineProperty(proxy, "foo", { value: "bar" });
    Reflect.defineProperty(proxy, "foo", { value: "bar" });
    // defineProperty();
    // defineProperty();
    
    // 捕获不变式
    // 如果目标对象不可扩展,则无法定义属性。
    // 如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性。
    // 如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    5. getOwnPropertyDescriptor()

    getOwnPropertyDescriptor()捕获器会在 Object.getOwnPropertyDescriptor()中被调
    用。对应的反射 API 方法为 Reflect.getOwnPropertyDescriptor()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @returns getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined
       */
      getOwnPropertyDescriptor(target, property) {
        console.log("getOwnPropertyDescriptor()");
        return Reflect.getOwnPropertyDescriptor(...arguments);
      },
    });
    
    // 拦截的操作
    Object.getOwnPropertyDescriptor(proxy, "foo");
    Reflect.getOwnPropertyDescriptor(proxy, "foo");
    // getOwnPropertyDescriptor();
    // getOwnPropertyDescriptor();
    
    // 捕获不变式
    // 如果自有的target.property 存在且不可配置,则处理程序必须返回一个表示该属性存在的
    // 对象。
    // 如果自有的target.property 存在且可配置,则处理程序必须返回表示该属性可配置的对象。
    // 如果自有的target.property 存在且target 不可扩展,则处理程序必须返回一个表示该属性存
    // 在的对象。
    // 如果target.property 不存在且target 不可扩展,则处理程序必须返回undefined 表示该属
    // 性不存在。
    // 如果target.property 不存在,则处理程序不能返回表示该属性可配置的对象。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    6. deleteProperty()

    deleteProperty()捕获器会在 delete 操作符中被调用。对应的反射 API 方法为 Reflect.
    deleteProperty()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} property 引用的目标对象上的字符串键属性
       * @returns deleteProperty()必须返回布尔值,表示删除属性是否成功
       */
      deleteProperty(target, property) {
        console.log("deleteProperty()");
        return Reflect.deleteProperty(...arguments);
      },
    });
    
    // 拦截的操作
    delete proxy.foo;
    delete proxy["foo"];
    Reflect.deleteProperty(proxy, "foo");
    // deleteProperty();
    // deleteProperty();
    // deleteProperty();
    
    // 捕获不变式
    // 如果自有的target.property 存在且不可配置,则处理程序不能删除这个属性。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    7. ownKeys()

    ownKeys()捕获器会在 Object.keys()及类似方法中被调用。对应的反射 API 方法为 Reflect.
    ownKeys()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @returns ownKeys()必须返回包含字符串或符号的可枚举对象。
       */
      ownKeys(target) {
        console.log("ownKeys()");
        return Reflect.ownKeys(...arguments);
      },
    });
    
    // 拦截的操作
    Object.getOwnPropertyNames(proxy);
    Object.getOwnPropertySymbols(proxy);
    Object.keys(proxy);
    Reflect.ownKeys(proxy);
    // ownKeys();
    // ownKeys();
    // ownKeys();
    // ownKeys();
    
    // 捕获不变式
    // 返回的可枚举对象必须包含target 的所有不可配置的自有属性。
    // 如果target 不可扩展,则返回可枚举对象必须准确地包含自有属性键。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    8. getPrototypeOf()

    getPrototypeOf()捕获器会在 Object.getPrototypeOf()中被调用。对应的反射 API 方法为
    Reflect.getPrototypeOf()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @returns getPrototypeOf()必须返回对象或null。
       */
      getPrototypeOf(target) {
        console.log("getPrototypeOf()");
        return Reflect.getPrototypeOf(...arguments);
      },
    });
    
    // 拦截的操作
    Object.getPrototypeOf(proxy);
    Reflect.getPrototypeOf(proxy);
    proxy.__proto__;
    Object.prototype.isPrototypeOf(proxy);
    proxy instanceof Object;
    // getPrototypeOf();
    // getPrototypeOf();
    // getPrototypeOf();
    // getPrototypeOf();
    // getPrototypeOf();
    
    // 捕获不变式
    // 如果target 不可扩展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object.
    // getPrototypeOf(target)的返回值。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    9. setPrototypeOf()

    setPrototypeOf()捕获器会在 Object.setPrototypeOf()中被调用。对应的反射 API 方法为
    Reflect.setPrototypeOf()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} prototype target 的替代原型,如果是顶级原型则为null。
       * @returns setPrototypeOf()必须返回布尔值,表示原型赋值是否成功
       */
      setPrototypeOf(target, prototype) {
        console.log("setPrototypeOf()");
        return Reflect.setPrototypeOf(...arguments);
      },
    });
    
    // 拦截的操作
    Object.setPrototypeOf(proxy, Object);
    Reflect.setPrototypeOf(proxy, Object);
    // setPrototypeOf();
    // setPrototypeOf();
    
    // 捕获不变式
    // 如果target 不可扩展,则唯一有效的prototype 参数就是Object.getPrototypeOf(target)
    // 的返回值。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    10. isExtensible()

    isExtensible()捕获器会在 Object.isExtensible()中被调用。对应的反射 API 方法为
    Reflect.isExtensible()。

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @returns isExtensible()必须返回布尔值,表示target 是否可扩展
       */
      isExtensible(target) {
        console.log("isExtensible()");
        return Reflect.isExtensible(...arguments);
      },
    });
    
    // 拦截的操作
    Object.isExtensible(proxy);
    Reflect.isExtensible(proxy);
    // isExtensible();
    // isExtensible();
    
    // 捕获不变式
    // 如果target 可扩展,则处理程序必须返回true。
    // 如果target 不可扩展,则处理程序必须返回false。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    11. preventExtensions()

    preventExtensions()捕获器会在 Object.preventExtensions()中被调用。对应的反射 API
    方法为 Reflect.preventExtensions()。preventExtensions 表示对象不能再添加新的属性,但是可以修改原有属性

    const myTarget = {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @returns preventExtensions()必须返回布尔值,表示target 是否已经不可扩展
       */
      preventExtensions(target) {
        console.log("preventExtensions()");
        return Reflect.preventExtensions(...arguments);
      },
    });
    
    // 拦截的操作
    Object.preventExtensions(proxy);
    Reflect.preventExtensions(proxy);
    // preventExtensions();
    // preventExtensions();
    
    // 捕获不变式
    // 如果Object.isExtensible(proxy)是false,则处理程序必须返回true。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    12. apply()

    apply()捕获器会在调用函数时中被调用。对应的反射 API 方法为 Reflect.apply()。

    const myTarget = () => {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标对象
       * @param {*} thisArg 调用函数时的this参数
       * @param {*} argumentsList 调用函数时的参数列表
       * @returns 返回值无限制
       */
      apply(target, thisArg, argumentsList) {
        console.log("apply()");
        return Reflect.apply(...arguments);
      },
    });
    
    // 拦截的操作
    proxy();
    proxy.call(proxy);
    proxy.apply(proxy);
    Reflect.apply(proxy, proxy, []);
    
    // 捕获不变式
    // target 必须是一个函数对象。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    13. construct()

    construct()捕获器会在 new 操作符中被调用。对应的反射 API 方法为 Reflect.construct()。

    const myTarget = function () {};
    
    const proxy = new Proxy(myTarget, {
      /**
       *
       * @param {*} target 目标构造函数
       * @param {*} argumentsList 传给目标构造函数的参数列表
       * @param {*} newTarget 代理构造函数
       * @returns construct()必须返回一个对象。
       */
      construct(target, argumentsList, newTarget) {
        console.log("construct()");
        console.log(newTarget === proxy);
        return Reflect.construct(...arguments);
      },
    });
    
    // 拦截的操作
    new proxy();
    Reflect.construct(proxy, []);
    // construct();
    // true;
    // construct();
    // true;
    
    // 捕获不变式
    // target 必须可以用作构造函数。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    代理模式

    使用代理可以在代码中实现一些有用的编程模式

    1.跟踪属性访问

    通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询。把实现相应捕获
    器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过

    const user = {
      name: "Dragon",
    };
    
    const proxy = new Proxy(user, {
      get(target, property, receiver) {
        console.log(`getting ${property}`);
        return Reflect.get(...arguments);
      },
      set(target, property, value, receiver) {
        console.log(`setting ${property} = ${value}`);
        return Reflect.set(...arguments);
      },
    });
    
    proxy.name; //getting name
    proxy.age = 28; //setting age = 28
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    2. 隐藏属性

    代理的内部实现对外部代码是不可见的,利用 get()和 has()来隐藏某些属性,使得访问该属性时 undefined

    const hiddenProperties = ["foo", "bar"];
    const targetObject = {
      foo: 1,
      bar: 2,
      baz: 3,
    };
    
    const proxy = new Proxy(targetObject, {
      get(target, property) {
        if (hiddenProperties.includes(property)) {
          return undefined;
        } else {
          return Reflect.get(...arguments);
        }
      },
      has(target, property) {
        if (hiddenProperties.includes(property)) {
          return false;
        } else {
          return Reflect.has(...arguments);
        }
      },
    });
    
    // 访问属性 get()
    console.log(proxy.foo); // undefined
    console.log(proxy.bar); // undefined
    console.log(proxy.baz); // 3
    
    // has()
    console.log("foo" in proxy); // false
    console.log("bar" in proxy); // false
    console.log("baz" in proxy); // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3. 属性验证

    所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值

    const target = {
      onlyNumberGoHere: 0,
    };
    const proxy = new Proxy(target, {
      set(target, property, value) {
        if (typeof value !== "number") {
          return false;
        } else {
          return Reflect.set(...arguments);
        }
      },
    });
    
    proxy.onlyNumberGoHere = 1;
    console.log(proxy.onlyNumberGoHere); //1
    proxy.onlyNumberGoHere = "2"; //这里设置了"2",不是数字,赋值操作失败
    console.log(proxy.onlyNumberGoHere); //1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4. 函数和构造函数参数验证

    跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查

    // 函数
    // 中位数
    function median(...nums) {
      return nums.sort()[Math.floor(nums.length / 2)];
    }
    
    const proxy = new Proxy(median, {
      apply(target, thisArg, argArray) {
        for (let item of argArray) {
          if (typeof item !== "number") {
            throw "Non-number argument provided";
          }
        }
        return Reflect.apply(...arguments);
      },
    });
    
    console.log(proxy(4, 7, 1)); //4
    console.log(proxy("4", 7, 1)); // Non-number argument provided
    
    // 构造函数
    class User {
      constructor(id) {
        this.id_ = id;
      }
    }
    
    const proxy = new Proxy(User, {
      construct(target, argArray) {
        if (argArray[0] === undefined) {
          throw new Error("User cannot be instantiated without id");
        } else {
          return Reflect.construct(...arguments);
        }
      },
    });
    new proxy(1);
    new proxy();
    //Error: User cannot be instantiated without id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    5. 数据绑定与可观察对象

    通过代理可以把运行时中原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的
    代码互操作

    //比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:
    const userList = [];
    class User {
      constructor(name) {
        this.name = name;
      }
    }
    
    const UserProxy = new Proxy(User, {
      construct() {
        const newUser = Reflect.construct(...arguments);
        userList.push(newUser);
        return newUser;
      },
    });
    
    new UserProxy("john");
    new UserProxy("jacob");
    new UserProxy("mathew");
    console.log(userList);
    // [
    //   User { name: 'john' },
    //   User { name: 'jacob' },
    //   User { name: 'mathew' }
    // ]
    
    //另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:
    const userList = [];
    function emit(newValue) {
      console.log(newValue);
    }
    const proxy = new Proxy(userList, {
      set(target, property, value, receiver) {
        const result = Reflect.set(...arguments);
        if (result) {
          emit(Reflect.get(target, property, receiver));
        }
        return result;
      },
    });
    proxy.push("John");
    // John
    // 1
    proxy.push("Jacob");
    // Jacob
    // 2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
  • 相关阅读:
    019-JAVA访问权限、封装详细讲解
    TCP协议:超时重传、流量控制、keep-alive和端口号,你真的了解吗?
    ConfigurationProperties注解详解
    Neo4J入门笔记
    Java并发编程核心概念
    Scalable Multi-Party Private Set-Intersection-解读
    传统防火墙与Web应用程序防火墙(WAF)的区别
    从k8s 的声明式API 到 GPT的 提示语
    自动生成图片及修改图片尺寸
    获取DataFrame中各列最大值所在行的行号(索引号)idxmax()
  • 原文地址:https://blog.csdn.net/qq_42372534/article/details/124621637