最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to execute a function before and after each class method call? - Stack Overflow

programmeradmin1浏览0评论

I want to insert on pre execute and post execute hooks on functions in javascript classes.

Lets say I have a class like this.

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

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

I want to define a before and after hook for these preexisting class methods. Something like this.

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;

What is the best way of implementing these hooks with minimal changes in existing code?

I want to insert on pre execute and post execute hooks on functions in javascript classes.

Lets say I have a class like this.

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

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

I want to define a before and after hook for these preexisting class methods. Something like this.

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;

What is the best way of implementing these hooks with minimal changes in existing code?

Share Improve this question asked Feb 23, 2020 at 15:01 Souradeep NandaSouradeep Nanda 3,2982 gold badges34 silver badges48 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 5

Here is a rough solution to the problem:

// 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;
  };
});

Please note that this code is not part of the class itself, execute it as a normal script.

And there is a good chance that this short proof of concept crashes on real-world classes and requires some additional checks and special-case handlers, especially to get proper logging output. But it works with you Foo class.

Here's the working example: https://codesandbox.io/s/great-fog-c803c

Proxy solution

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
*/

Just wanted to share because I've read in ments that someone had problem to set Proxy. You can read more about Proxies here, and here more about apply.

Remember that in proxy handle, the this is actually this.main. For better understanding, you can change it to classInstance or something similar.

发布评论

评论列表(0)

  1. 暂无评论