【问题标题】:mixing constructor and apply traps on the Javascript Proxy object混合构造函数并在 Javascript 代理对象上应用陷阱
【发布时间】:2018-11-23 08:17:21
【问题描述】:

我有一个类,我想对其应用代理,观察方法调用和构造函数调用:

Calculator.js

class Calc {
  constructor(){}

  add(a, b) {
    return a+b;
  }

  minus(a, b) {
    return a-b;
  }
}

module.exports = Calc;

index.js

const Calculator = require('./src/Calculator');

const CalculatorLogger = {
  construct: function(target, args, newTarget) {
      console.log('Object instantiated');
      return new target(...args);
  },
  apply: function(target, thisArg, argumentsList) {
      console.log('Method called');
  }
}
const LoggedCalculator = new Proxy(Calculator, CalculatorLogger);
const calculator = new LoggedCalculator();
console.log(calculator.add(1,2));

当它被调用时,我希望输出是:

对象实例化

方法调用

但是,apply 没有被调用,我认为这是因为我将代理附加到 Calculator 类,而不是实例化对象,因此不知道 apply 陷阱。

我如何构建一个包罗万象的代理来“观察”方法调用和构造函数调用。

【问题讨论】:

  • 你甚至没有应用 calculator 对象(它不是一个函数)。您正在应用add 方法。
  • 仅供参考,您应该尽可能在代理处理程序中尝试使用Reflect,所以在这里您需要Reflect.construct而不是new target。

标签: javascript ecmascript-6 proxy-pattern


【解决方案1】:

不要在类上使用handler.apply(),而是修改handler.construct() 返回的内容,为其添加一个代理。

class originalClass {
  constructor() {
    this.c = 1;
  }
  add(a, b) {
    return a + b + this.c;
  }
}
const proxiedClass = new Proxy(originalClass, {
  construct(target, args) {
    console.log("constructor of originalClass called.");
    return new Proxy(new target(...args), {
      get(target, prop, receiver) {
        console.log(prop + " accessed on an instance of originalClass");
        const val = target[prop];
        if (typeof target[prop] === "function") {
          console.log(prop + " was a function");
          return function(...args) {
            console.log(prop + "() called");
            return val.apply(this, args);
          };
        } else {
          return val;
        }
      }
    });
  }
});
const proxiedInstance = new proxiedClass();
console.log(proxiedInstance.add(1, 2));

这里有 2 个代理:

  • 一个代理来观察构造函数调用,并用...包装由该构造函数创建的任何实例。
  • ...观察属性访问的代理,并在这些属性是函数时进行记录。它还将包装任何函数,因此它可以观察对该函数的调用。

【讨论】:

  • 请注意,访问函数值属性不一定与调用它相同
  • 谢谢@Bergi。我已经编辑了包装函数的答案。
  • @uber5001 这是一个很好的答案,但您可能应该通过以下改进对其进行更新:1) 使用 Reflect.construct 而不是新目标 2) 包装函数时,调用 val.apply(target, args) - 这将允许函数调用引用原始对象而不是代理。否则,对象实现将在每个 this 取消引用时继续通过代理。这可能需要成为一个选项,具体取决于任务的要求。
【解决方案2】:

我认为这是因为我将代理附加到 Calculator 类,而不是实例化对象,因此不知道应用陷阱。

完全正确,代理作用于对象,因此除非调用 Calculator 类的函数属性,否则它不会调用 apply,如下所示:

class Calculator {
  constructor() {
    this.x = 1;
  }

  instanceFunction() {
    console.log('Instance function called');
  }

  static staticFun() {
    console.log('Static Function called');
  }

}

const calcHandler = {
  construct(target, args) {
    console.log('Calculator constructor called');
    return new target(...args);
  },
  apply: function(target, thisArg, argumentsList) {
    console.log('Function called');
    return target(...argumentsList);
  }
};

Calculator = new Proxy(Calculator, calcHandler);

Calculator.staticFun();

const obj = new Calculator();

obj.instanceFunction();

清楚地表明,您可以通过代理包装Calculator 的实例:

  1. 让类代理代理construct 上的实例:

const CalculatorInstanceHandler = {
  apply(target, thisArg, args) {
    console.log('Function called');
    return target(...args);
  }
}

const CalculatorClassHandler = {
  construct(target, args) {
    const instance = new target(...args);
    return new Proxy(instance, CalculatorInstanceHandler);
  }
}
  1. 在Calculator 类中有一个工厂函数,以便创建代理 实例:

const CalculatorInstanceHandler = {
  apply(target, thisArg, args) {
    return target(...args);
  }
};

class Calculator {

  static getNewCalculator() {
    const instance = new Calculator();

    return new Proxy(instance, CalculatorInstanceHandler);

  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-28
    • 2020-12-06
    • 1970-01-01
    • 2017-12-30
    • 1970-01-01
    • 2018-01-24
    • 2013-11-21
    • 2018-06-11
    相关资源
    最近更新 更多