如何在每个 class 方法调用前后执行函数?

How to execute a function before and after each class method call?

我想在 javascript classes.classes.post 函数上插入预执行和 post 执行挂钩

假设我有一个这样的class。

class Foo {
  method1(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
  }

  method2(p3) {
    this.p3 = p3;
  }
}

我想为这些预先存在的 class 方法定义前后钩子。像这样。

class Foo {
  before(funName, ...params){
    // Should print ('method1', [p1, p2]) when method 1 is called
    // and ('method2', [p3]) when method 2 is called
    console.log(funName, params)
  }
  after(funName, result){
    // Should print the function name followed by its result
    console.log(funName, result)
  }
  method1(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
  }
  method2(p3) {
    this.p3 = p3;
  }
}

export default Foo;

在对现有代码进行最少更改的情况下实现这些挂钩的最佳方法是什么?

问题的粗略解决方法如下:

// we iterate over all method names
Object.getOwnPropertyNames(Foo.prototype).forEach((name) => {

  // First to do: we save the original method. Adding it to prototype
  // is a good idea, we keep 'method1' as '_method1' and so on
  Foo.prototype['_' + name] = Foo.prototype[name];

  // Next, we replace the original method with one that does the logging
  // before and after method execution. 
  Foo.prototype[name] = function() {

    // all arguments that the method receives are in the 'arguments' object
    console.log(`Method call: method1(${Object.values(arguments).join(', ')})`);

    // now we call the original method, _method1, on this with all arguments we received
    // this is probably the most confusing line of code here ;)
    // (I never user this['method'] before - but it works)
    const result = this['_' + name](...arguments);

    // here is the post-execution logging
    console.log(`Method result: ${result}`);

    // and we need to return the original result of the method
    return result;
  };
});

请注意,此代码不是 class 本身的一部分,请将其作为普通脚本执行。

而且这个简短的概念证明很可能会在现实世界中崩溃 classes 并且需要一些额外的检查和特殊情况处理程序,特别是为了获得正确的日志输出。但它适用于你 Foo class.

这是工作示例:https://codesandbox.io/s/great-fog-c803c

代理解决方案

class Foo {
  classAlias = false;

  proxyHandle = {
    // little hack where we save refrenece to our class inside the object
    main : this,
    /**
     * The apply will be fired each time the function is called
     * @param  target Called function
     * @param  scope  Scope from where function was called
     * @param  args   Arguments passed to function
     * @return        Results of the function
     */
    apply : function (target, scope, args) {
      const func_name = target.name;

      console.log('before', func_name);

      // let's call some method of this class to actually check if this is the right instance
      // also remember that you have to exclude methods which you are gonna use
      // inside here to avoid “too much recursion” error
      this.main._free.instanceCheck();


      // here we bind method with our class by accessing reference to instance
      const results = target.bind(this.main)(...args);

      console.log('after', func_name, results);
      return results;
    }
  }

  constructor(classAlias) {
    // Get all methods of choosen class
    let methods = Object.getOwnPropertyNames( Foo.prototype );

    // Find and remove constructor as we don't need Proxy on it
    let consIndex = methods.indexOf('constructor');
    if ( consIndex > -1 ) methods.splice(consIndex, 1);

    // Replace all methods with Proxy methods
    methods.forEach( methodName => {
      this[methodName] = new Proxy( this[methodName], this.proxyHandle );
    });
    this.classAlias = classAlias;
  }

  // easies trick to do to avoid infinite loop inside apply is to set your functions
  // inside object, as getOwnPropertyNames from prototype will only get methods
  _free = {
    instanceCheck : () => {
      // this will check if this is our Foo class
      console.log(this.classAlias);
    }
  }

  log() {
    return 'Result';
  }
}

(new Foo('Proxy Class A')).log();

/* 
  Output:
    before log
    Proxy Class A
    after log Result
*/

只是想分享,因为我在评论中看到有人在设置代理时遇到问题。您可以阅读有关代理的更多信息 here, and here 更多有关应用的信息。

请记住,在代理句柄中,this 实际上是 this.main。为了更好地理解,您可以将其更改为 classInstance 或类似的内容。