Modify the Constructor Function of a Class - javascript

I already have a function for modifing other class methods:
class MyClass {
constructor() {
this.num = 1;
}
inc(){
this.num++;
}
}
runAfterMethod(classHandle, methodName, executeAfter) {
const oldHandle = classHandle.prototype[methodName];
classHandle.prototype[methodName] = function () {
const returnValue = oldHandle.apply(this, arguments);
//#ts-ignore
executeAfter.apply(this, arguments);
return returnValue;
};
}
runAfterMethod(MyClass, "inc", function() {console.log(this.num)})
new MyClass().inc(); // Prints 2
However, this does not work for the constructor of the class (because it is technically the class itself)
What I would really be happy with is something that mimics this behavior:
class MyClass {
constructor() {
this.num = 1;
}
}
extendConstructor(MyClass, function(){
this.str = "hello world"
})
new MyClass(); // num: 1, str: "hello world"
I have looked at things like How to modify the constructor of an ES6 class however, they all require = somewhere, which does not work inside of a function (where that only changes the value of the class inside the function, not the class itself)

Mutating the existing class is really weird. What would be much less of a code smell IMO would be to create a wrapper around the class that instantiates the class, then calls it with your custom callback, and then returns the instance - it'd be a lot more straightforward. Would that work for your purposes?
class MyClass {
constructor() {
this.num = 1;
}
}
const extendConstructor = (theClass, callback) => function(...args) {
const instance = new theClass(...args);
callback.call(instance);
return instance;
};
const Extended = extendConstructor(MyClass, function(){
this.str = "hello world"
})
console.log(new Extended()); // num: 1, str: "hello world"

Related

Publish subscriber pattern using instance based decorators 'this' is always undefined

class Observable {
constructor() {
this.handlers = [];
}
publish(value) {
this.handlers.forEach(handler => {
handler(value);
});
}
subscribe(callback) {
this.handlers.push(callback);
}
}
const concreteObserver = new Observable();
function Subscribe(observable) {
return function functionDescriptor(target, propertyKey, descriptor) {
observable.subscribe(target[propertyKey]);
return descriptor;
}
}
class MyClass {
constructor(){
this.x = 5;
}
#Subscribe(concreteObserver)
subsribeToValue(value) {
console.log(this.x); // undefined
}
}
As you can see, the subscribe function is called each time, someone calls concreteObserver.publish() however, when you call observable.subscribe(target[propertyKey]); then 'this' becomes undefined.
I also tried overriding the descriptor getter, and calling that one, but i still get undefined. On classes i was able to wrap a function by calling target.prototype.functionName.
This works when i know what the function name will be called, but the function name for #Subscribe can be arbitrary, so i can't use it on a class level decorator unless i use Reflection to detect all the annotations of the class.
EDIT
Tried so far
observable.subscribe(target[propertyKey].bind(this));
which returns undefined, subscribe has the right context in this case.
observable.subscribe(data => descriptor.value.apply(this, data)); also has 'this' as undefined
descriptor.value = function(){
console.log(this); //undefined
}
descriptor.get = function(){
console.log(this); //undefined
}
The solution i came up with. Since it is only possible to get the instance of a class in the class decorator, then that is where this can be used properly, in the the subscribe function i tell what function i should subscribe to, then in the ClassDecorator i iterate through each method to determine if they have __subscribeFunction in their prototype and thus subscribe to the method while binding instance
class Observable {
constructor() {
this.handlers = [];
}
publish(value) {
this.handlers.forEach(handler => {
handler(value);
});
}
subscribe(callback) {
this.handlers.push(callback);
}
}
const concreteObserver = new Observable();
function ClassDecorator(target) {
const originalTarget = target;
const Override = function (...args) {
const instance = originalTarget.apply(this, args);
Object.values(instance.__proto__).forEach(method => {
const observableFunction = method.prototype.__subscribeFunction;
if (observableFunction) {
observableFunction.subscribe(method.bind(instance));
}
});
return instance;
};
Override.prototype = originalTarget.prototype;
customElements.define(elementName, target);
return Override;
}
function Subscribe(observable) {
return function functionDescriptor(target, propertyKey, descriptor) {
target[propertyKey].prototype.__subscribeFunction = observable;
}
}
#ClassDecorator
class MyClass {
constructor(){
this.x = 5;
}
#Subscribe(concreteObserver)
subsribeToValue(value) {
console.log(this.x); // 5
}
}
This doesn't work because the decorator is called when the class itself is constructed, but before any instance is created. Since there's no instance, there can't be a this – you only have access to the prototype, but class properties aren't on the prototype (unlike methods).
You can verify this using this example:
function Example() {
console.log("#Example initialized");
return function exampleDescriptior(target, propertyKey, descriptor) {
console.log("#Example called");
}
}
console.log("Before declaring class");
class Test {
#Example()
public test() {}
}
console.log("After declaring class");
console.log("Before creating instance");
const test = new Test();
console.log("After creating instance");
console.log("Before calling method");
test.test();
console.log("After calling method");
which yields the output
Before declaring class
#Example initialized
#Example called
After declaring class
Before creating instance
After creating instance
Before calling method
After calling method
That said, what you can do is write another decorator applied on, say, class level which proxies the constructor. If your #Subscribe annotation stores some meta-data on the prototype, the class decorator could then look for it and do the actual wiring. So getting something like
#AutoSubscribe()
class MyClass {
#Subscribe(observer)
subscribe(value) {
console.log(this.x);
}
}
to work should be possible. In fact, you could maybe even get rid of the second decorator by proxying the constructor from the #Subscribe decorator, but you'd still have to store metadata that you can look through during instantiation.

extending singleton class in es6

I need to extend a singleton class in JavaScript .
The problem that I am facing is that I get the class instance which I am extending from instead of only getting the methods of the class.
I have tried to remove super to not get the instance but then I got an error
Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Code example:
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
if (instanceA === null) {
this.options = options;
instanceA = this;
}
return instanceA;
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super()
console.log('real class is ' + this.constructor.name)
this.options = options
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});
const b = new B({
i_am_b: "bbbbbb"
}) // this change a
console.log(b.options)
console.log(a.options)
So, first of all there's a misconception here:
I have tried to remove super to not get the instance but then I got an error
super() calls the parent class' constructor on the created instance of the child class (i.e. what this is referencing). It does not return a parent class instance. See here for more information.
So, calling super() does not violate the singleton property of the parent class at all. It may well be only constructed a single time if implemented correctly.
With that in mind, you should improve your code a little bit.
A sensible change would be to remove the instance management from the constructors. One solution would be to use static constructors which either create the singleton if no instance exists or return the created instance.
Another one is to drop the arguments to the singleton class constructors. It doesn't really make sense to pass arguments to a class which is supposed to be instantiated once (you're never gonna do anything with the constructor arguments again). You could just make the arguments properties of the singleton right away. Here's a SO answer supporting this point for Java singletons.
A complete example with static constructors and without arguments looks like this:
let instanceA = null;
let instanceB = null;
let counters = { A: 0, B: 0 }; // count class instantiations
class A {
static getInstance() {
if (instanceA === null) {
instanceA = new A();
}
return instanceA;
}
whoami() {
const name = this.constructor.name;
return `${name} #${counters[name]}`;
}
constructor() {
counters[this.constructor.name] += 1;
}
}
class B extends A {
static getInstance() {
if (instanceB === null) {
instanceB = new B();
}
return instanceB;
}
constructor() {
super();
}
}
const a1 = A.getInstance();
const a2 = A.getInstance();
const a3 = A.getInstance();
const b1 = B.getInstance();
const b2 = B.getInstance();
const b3 = B.getInstance();
console.log(a1.whoami());
console.log(a2.whoami());
console.log(a3.whoami());
console.log(b1.whoami());
console.log(b2.whoami());
console.log(b3.whoami());
Note that B inherits whoami from A and that the constructor call counters are never incremented past 1.
Obviously with this approach you can make no guarantee the singleton property holds for each class unless only the static constructors are used to generate instances (since the constructors are still accessible). I think it's a good compromise though.
In JavaScript, a singleton is just an object literal.
const a = {
options: {
i_am_a: "aaaaaa"
}
};
const b = {
options: {
i_am_b: "bbbbbb"
}
};
If you really need a constructor function, you can just write a function that returns an object.
function makeSingleton(options) {
return {
options
}
}
const a = makeSingleton({i_am_a: "aaaaaa"});
const b = makeSingleton({i_am_b: "bbbbbb"});
There's no inheritance chain here, just two object literals. If you absolutely need a class, you can just create one, but it's an unnecessary waste of resources and typing.
class Singleton {
constructor(options) {
this.options = options;
}
}
const a = new Singleton({i_am_a: "aaaaaa"});
const b = new Singleton({i_am_b: "bbbbbb"});
In terms of inheriting, if that's something you really need, you can use Object.create() or Object.assign(), depending on your needs. Be aware that both are shallow - they only work a single layer deep so modifying the child's options property would modify the parent's options property as well.
const a = {
options: {
i_am_a: "aaaaaa"
},
getOptions() {
return this.options;
}
};
const b = Object.create(a);
b.options.i_am_b: "bbbbbb";
a.options.i_am_b; // -> "bbbbbb"
b.getOptions(); // -> { i_am_a: "aaaaaa", i_am_b: "bbbbbb" }
Of course, you could use Object.create() or Object.assign() on the options as well.
To be honest, I think you either need a couple of instances of the same class, or a simple object literal without any inheritance.
const instances = {}
class Singleton {
constructor() {
const instance = instances[this.constructor];
if (instance == null) {
return instances[this.constructor] = this;
}
return instance;
}
}
class Foo extends Singleton {
constructor() {
super();
this.foo = "foo";
}
}
class Bar extends Singleton {
constructor() {
super();
this.foo = "bar";
}
}
const foo1 = new Foo();
const foo2 = new Foo();
const bar1 = new Bar();
const bar2 = new Bar();
console.log(foo1 === foo2, bar1 === bar2, foo1 === bar1, foo1.foo = 123, foo2, bar1);
well i don't know if it the best solution but what i did is to check if the constructor name is different then the class name. if so then i let it create a new instance because that mean i try to extend the class
here is a working example of my test
let instanceA = null;
let instanceB = null;
class A {
constructor(options) {
this.options = options;
if (instanceA === null) {
instanceA = this;
}
if(this.constructor.name !== "A"){
return this;
}
return instanceA;
}
method1(){
console.log(this.constructor.name)
}
}
class B extends A {
constructor(options) {
if (instanceB === null) {
super(options)
instanceB = this;
}
return instanceB;
}
}
const a = new A({
i_am_a: "aaaaaa"
});a
const b = new B({
i_am_b: "bbbbbb"
})
const c = new A({
i_am_c: "ccccc"
});
const d = new B({
i_am_d: "ddddd"
})
console.log(a.options)
console.log(b.options)
console.log(c.options)
console.log(d.options)
a.method1();
b.method1();
c.method1();
d.method1();

Capture `this` of caller inside class in JavaScript

Consider the following code:
class Abc {
funcAbc() {
console.log(this);
}
}
const abc = new Abc();
class Def {
constructor(func) {
this.func = func;
}
runFunc() {
this.func();
}
}
const def = new Def(abc.funcAbc);
def.runFunc();
I want this to be Abc when runFunc is called, but in the above implementation, this inside runFunc refers to Def. I understand this is happening because runFunc is a member of class Def. But is there any way I can capture 'original this', i.e. point this to Abc inside runFunc?
I cannot do const def = new Def(abc.funcAbc.bind(abc) because the consumer of class should not be bothered about setting the this context.
Note: This is not a theoretical question, it is an actual requirement in the project I am working on. The wrapper class takes in config, a part of which is a function. This function can also be a method on a class instance, using this inside it. Hence I need to preserve original this when calling the function from inside the wrapper class Def.
You are looking for bind() (or one of its variants).
class A {
constructor() {
this.hello = "hello";
}
run() {
console.log(this.hello);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run.bind(a));
b.run();
When you bind() a function, it locks what this will be inside the function when the function is run, regardless of how it is invoked.
You could also wrap up the function in a closure:
class A {
constructor() {
this.hello = "hello";
this.run = ((self) => () => console.log(self.hello))(this);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run);
b.run();
Or you could use the arrow function syntax. Note, this one requires the Babel class properties transform plugin to work:
class A {
constructor() {
this.hello = "hello";
}
run = () => {
console.log(this.hello);
}
}
class B {
constructor(func) {
this.func = func;
}
run() {
this.func();
}
}
const a = new A();
const b = new B(a.run);
b.run();
Using function.call(thisArg, args...) can set this to what ever you want. i.e., you could do:
class Abc {
func() {
console.log(this);
}
}
const abc = new Abc();
class Def {
constructor(source) {
this.func = source.func;
this.source = source;
}
runFunc() {
this.func.call(this.source);
}
}
const def = new Def(abc);
def.runFunc();
This solution assumes there will be a method on abc that is called func.

Calling class instance as a function in JavaScript

Assuming I have a class
class foo {
constructor() {
this._pos = 0;
}
bar(arg) {
console.log(arg);
}
}
const obj = new foo();
How do I make it possible to call:
let var1 = obj('something');
You can make a callable object by extending the Function constructor, though if you want it to access the instance created, you'll actually need to create a bound function in the constructor that binds the instance to a function that is returned.
class foo extends Function {
constructor() {
super("...args", "return this.bar(...args)");
this._pos = 0;
return this.bind(this);
}
bar(arg) {
console.log(arg + this._pos);
}
}
const obj = new foo();
let var1 = obj('something ');

Promoting object in JavaScript

So I have a class and another class that extends the first one.
I would like to know if it's possible to promote the extended class from the first one in JavaScript.
class Class1 {
constructor(data) {
this.var1 = data.var1
this.var2 = data.var2
}
}
class Class2 extends Class1 {
constructor(o) {
this = o
this.var3 = '!!!'
}
}
const o = new Class1({var1: 'HELLO', var2: 'WORLD'})
const o2 = new Class2(o)
console.log(o2.var1)
console.log(o2.var2)
I know that this = o is going to throw an error. But is there a way to accomplish the task without having to assign every field from the old object to a new one?
You can use the super() function:
class Class1 {
constructor(data) {
this.var1 = data.var1
this.var2 = data.var2
}
}
class Class2 extends Class1 {
constructor(o) {
super(o)
this.var3 = '!!!'
}
}
const o = new Class1({var1: 'HELLO', var2: 'WORLD'})
const o2 = new Class2(o)
console.log(o2.var1) // -> HELLO
console.log(o2.var2) // -> WORLD
More info on super(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super
You can call super(o) at the beginning of the constructor on class2
Demo fiddle

Categories

Resources