Home > Software design >  Modifying method functionality of JS classes
Modifying method functionality of JS classes

Time:08-31

I have this problem that I'm trying to wrap my head around and was wondering if any JS wizards could help me out.

I want to write a factory function that takes a class as an input and returns a new class with all of the same methods as the input class, but with some logging added to each method.

I'm thinking I could loop over each method in the prototype, save the old method, and then reassigned the method to a new function that calls the old method along with the logging as well. However, I'm not sure how I would even get started on this.

Could anyone help me out?

CodePudding user response:

You can grab the method names with getOwnPropertyNames, then modify the value of these descriptors, and redefine them on the new class.

class MyClass {
  foo() { return "foo"; }
  
  bar() { return "bar"; }
}

function logging(cls) {
  const c = class extends cls {}; // extend old class
  
  Object
    .getOwnPropertyNames(cls.prototype)
    .filter((v) => (
      v !== "constructor" && // ignore 'constructor'
      typeof cls.prototype[v] === "function" // only functions (methods)
    ))
    .forEach((key) => {
    const desc = Object.getOwnPropertyDescriptor(cls.prototype, key);
    
    const method = desc.value; // store old method
    
    desc.value = function (...args) {
      console.log(key, "was called");
    
      return method.apply(this, args); // call old method with correct 'this'
    };
    
    Object.defineProperty(c.prototype, key, desc); // redefine on new class
  });
  
  return c; // returning the new class
}

new (logging(MyClass))().foo(); // foo was called

CodePudding user response:

You could try something like this. Here, Square extends a parent class, calling the base implementation and then its logs on top.

class Rectangle {
  constructor(height, width) {
    this.name = 'Rectangle';
    this.height = height;
    this.width = width;
  }
  sayName() {
    console.log(`Hi, I am a ${this.name}.`);
  }
  get area() {
    return this.height * this.width;
  }
  set area(value) {
    this._area = value;
  }
}

class Square extends Rectangle {
  constructor(length) {

    // Here, it calls the parent class's constructor with lengths
    // provided for the Rectangle's width and height
    super(length, length);

  }
  
  sayName() {
      super.sayName();
      console.log("logs go here")
  }
}

CodePudding user response:

you can also crated a method to set params through which you set new/update methods & data, and by using a static class or a builder function you can copy methods without crossreferensing them. e. g.

const addMethod = (key, options, target) => {
  let col = {
    foo(arg, options) {
      //..
    }, 
    bar(arg, options) {
      //..
    }
  } 

  target[key] = col[key].bind(target)
}


const extendClass = (target, source) => {
  let arr = [] 

  for (let key in source) 
    if (typeof source[key] === 'function' && !target[key]) 
      arr.push({ key, options: {}) 

  // new target(arr) // new instance of target class
  target.setParams(arr) // extend instance

  return target
}

class A {
  //.. predifined stuff
  constructor(params) {
    setParams(params) 
  } 

  setParams(params) {
    for (let method of params)
      addMethod(method.key, method.options, this) 
    //.. and so on, make sure to condition different actions properly, this can also live mostly in the extendClass function
  } 
}

class B {
  // same same but diff
}

const extendedInstance = extendClass(A, B) 
  • Related