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)