Switch methods in object using protoype - javascript

I have a piece of code that switches the processing used on an object, a, outside of a function test. The function, test, uses properties of a and a.b and a.c:
let a;
let fB = {method: function(o){o.b.count++;return o.b.value}};
let fC = {method: function(o){o.c.count++;return o.c.value}};
a = new class A {value='Method:'; b={value:'B',count:0}; c={value:'C',count:0}};
function test (a,f) {
console.log(a.value+f.method(a));
}
test(a,fB); // method:B
test(a,fC); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1
I want to write it in a more OO way, something like :
function test (a) {
console.log(a.value+a.method());
}
class B {
constructor(){this.val='B';this.count=0}
method (){this.count++;return this.val}
}
class C {
constructor(){this.val='C';this.count=0}
method (){this.count++;return this.val}
}
class A {
value='Method:';
}
a = new A();
Object.setPrototypeOf(a,new B());
test(a); // method:B
Object.setPrototypeOf(a,new C());
test(a); // method:C
console.log('Counts:',a.b.count,a.c.count) // a.b is undefined
The best I could come up with was :
class B {
constructor(){this.value='B';this.count=0}
method (){this.count++;return this.value}
}
class C {
constructor(){this.value='C';this.count=0}
method (){this.count++;return this.value}
}
class A {
value='Method:';
b=new B();
c=new C();
x=this.b;
switchToB(){this.x = this.b}
switchToC(){this.x = this.c}
method(){return this.x.method()}
}
a = new A();
function test (a) {
console.log(a.value+a.method());
}
test(a); // method:B
a.switchToC();
test(a); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1
I don't much like this, nor an alternative :
class B {
constructor(a){this.value=a.value;this.val='B';this.count=0}
method (){this.count++;return this.val}
}
class C {
constructor(a){this.value=a.value;this.val='C';this.count=0}
method (){this.count++;return this.val}
}
class A {
value='Method:';
b=new BB(this);
c=new CC(this);
}
a = new A();
test(a.b); // method:B
test(a.c); // method:C
console.log('Counts:',a.b.count,a.c.count) // Counts: 1 1
Is there a better way to do this in JS ?

Looking at the first OO attempt that you provided, there are these observations:
No b or c properties are created on a, yet the testing code needs them.
Also the switching mechanism should work with a reference to these b and c properties
B and C are look-alikes so that could be generalised to one Counter class whose constructor takes an argument that distinguishes them (determining the value property).
We could also think of a more lazy initialisation, where a counter object is only created when a switches to it.
There seems to be a relationship between the name "b" and the output "B". Similar between "c" and "C". So one could be derived from the other.
This leads to the following implementation:
class Counter {
constructor(value) {
this.value = value.toUpperCase();
this.count = 0;
}
method() {
this.count++;
return this.value;
}
}
class A {
constructor() {
this.value = 'Method:';
}
activateCounter(name) {
// Create a new counter if not used before
this[name] ??= new Counter(name);
// Redirect the method to use this counter's method
this.method = () => this[name].method();
}
}
function test(a) {
console.log(a.value + a.method());
}
let a = new A();
a.activateCounter("b");
test(a); // method:B
a.activateCounter("c");
test(a); // method:C
console.log('Counts:', a.b.count, a.c.count) // Counts: 1 1
Here a.method() should only be called after a counter was activated with a call of a.activateCounter. This is because the latter will (re)define the method method on a.

Related

How to use instanceof with a class which is not defined in the current context?

This is going to be a bit tricky but I'll do my best to explain,
Consider the following code:
class A { a() { return true; } }
class B { b() { return new A(); } }
var b = new B();
console.log(b instanceof B); // true
console.log(b.b() instanceof A); // true <--- [1]
It's pretty straightforward to see that (class B).b() is going to return an instance of an object of type(/class) A. And we can evaluate this using the instanceof operator [1].
Now, a problem arises when, for whatever reason, we do not have a definition for class A in our current scope. One scenario where such thing may happen, is when you import/require an object from a library and many of its internal classes are not exposed.
Since there is no definition for A, it is not possible to do <symbol> instanceof A ...
So, how may one actually perform this check under such scenario?
PS: I already tried the Object.prototype.toString... trick to not avail.
You could create a function that traverses the prototype chain and returns a list of all the super classes of an object including the class from which the object was instantiated from.
class Foo {}
class X extends Foo{}
class A extends X{}
class B { b() { return new A(); } }
function getParents(obj) {
const arr = [];
while (obj = Reflect.getPrototypeOf(obj)) {
arr.push(obj.constructor.name);
}
return arr;
}
var b = new B().b();
const parents = getParents(b);
console.log(`b instance of A = ${parents.includes('A')}`);
console.log(`b instance of X = ${parents.includes('X')}`);
console.log(`b instance of Foo = ${parents.includes('Foo')}`);
console.log(`b instance of Object = ${parents.includes('Object')}`);
You could also do this using a recursive function
class Foo {}
class X extends Foo{}
class A extends X{}
class B { b() { return new A(); } }
function getParents(obj, arr = null) {
if (!arr) arr = [];
const protoTypeObj = Reflect.getPrototypeOf(obj);
if (!protoTypeObj) return;
arr.push(protoTypeObj.constructor.name);
getParents(protoTypeObj, arr);
return arr;
}
var b = new B().b();
const parents = getParents(b);
console.log(`b instance of A = ${parents.includes('A')}`);
console.log(`b instance of X = ${parents.includes('X')}`);
console.log(`b instance of Foo = ${parents.includes('Foo')}`);
console.log(`b instance of Object = ${parents.includes('Object')}`);

How to override the JS getter & setter function using prototypal inheritance?

Need to know how to override the getters and setters by extending using prototypal inheritance
Following is an example to understand the issue
function A() {
this._loading = "Welcome";
}
Object.defineProperty(A.prototype, 'loading', {
get() {
return this._loading+" from A";
},
set(value) {
this._loading = value;
}
});
function B() {}
Object.defineProperty(B.prototype, 'loading', {
get() {
return this._loading+" from B";
},
set(value) {
this._loading = value;
}
});
var a=new A();
var b=new B();
b.__proto__=a; // Not sure if this is wrong. This assigns the whole prototype of A to B so that B is submerged
My expected result is b.loading should return me
Welcome from B
Instead its returning
Welcome from A
Help is appreciated.
Welcome to stackoverflow ;)
In Javascript you have different way to handle "inheritance".
As you said, your implementation is not working because of the proto reassignation.
1. Overriding prototype
Usually it is considered bad practices to access prototype using __proto__. In your case you are re-assigning the whole prototype of B, so it will just replace all prototype.
// Here you are overriding the prototype of "b"
b.__proto__=a;
// It refers to the prototype of a, so it the equivalent of calling a.loading
b.loading
2. Inherits from other prototype - ES5
function B() {
// Call the constructor of A giving the "B.this" context
A.call(this)
}
// Inherits from prototype of A
B.prototype = Object.create(A.prototype)
Object.defineProperty(B.prototype, 'loading', {
get() {
return this._loading+" from B";
}
});
// You won't need to instanciate A to inherits from it
const b = new B();
b.loading // "Welcome from B"
3. A step further
In a real world scenario you may want to inherits some properties from A, and override other properties in B. Because B inherits from A, you can call both methods from B.prototype and A.prototype.
function A() {
this._loading = "Welcome";
}
Object.defineProperty(A.prototype, "loadingA", {
get: function loading() {
return this._loading + " from A"
}
});
Object.defineProperty(A.prototype, "loading", {
get: function loading() {
return this._loading + " from A"
}
});
function B() {
A.call(this)
}
B.prototype = Object.create(A.prototype)
Object.defineProperty(B.prototype, 'loading', {
get() {
return this._loading+" from B";
}
});
var b = new B()
b.loading // Welcome from B
b.loadingA // Welcome from A
4. A second step further - ES6+
You may want to use ES6 class syntax sugar, it's less verbose and easier to understand ;)
class A {
constructor() {
this._loading = "Welcome"
}
get loading() {
return this._loading + " from A"
}
}
class B extends A {
get loading() {
return this._loading + " from B"
}
}
b.loading // Welcome from B
b.loadingA // Welcome from A
Further readings on javascript inheritance

In JavaScript, specifically ES6 on NodeJS, can I manipulate getter and setter functions of a class directly at runtime?

Lets say I have a class defined as follows:
class MyClass {
constructor(a, b) {
this._a = a;
this._b = b;
}
get a() {
return this._a;
}
set a(val) {
this._a = val;
}
add() {
return this._a + this._b;
}
}
I want to be able to access and manipulate the getter and setter functions directly at runtime in order to wrap them in additional debugging code. With the 'add' function, I can do this:
let oldAdd = MyClass.prototype.add;
MyClass.prototype.add = function() {
console.log('add called!');
let result = oldAdd.call(this);
console.log('add result: ' + result);
return result;
}
However, I cannot find a way to modify the getter and setter functions in a similar way.
I have tried
let propDef = Reflect.getOwnPropertyDescriptor(MyClass.prototype, 'a');
propDef.get = function() {
// ...
}
But this change does not actually get applied.
Any ideas?
I am also interested to know if it's possible to access and modify the constructor function in the same way.
Yes, you can do that by reconfiguring the properties. Here's an example (see comments):
class MyClass {
constructor(a, b) {
this._a = a;
this._b = b;
}
get a() {
return this._a;
}
set a(val) {
this._a = val;
}
add() {
return this._a + this._b;
}
}
// Use it before redefining
const instance = new MyClass(1, 2);
console.log(instance.a); // 1
instance.a = 2;
console.log(instance.a); // 2
// Redefine the property
const desc = Reflect.getOwnPropertyDescriptor(MyClass.prototype, "a");
const {get: originalGet, set: originalSet} = desc;
desc.get = function() {
const value = originalGet.call(this);
console.log("Debugging 'get' here, a's value is " + value);
return value;
};
desc.set = function(newValue) {
console.log("Debugging 'set' here, a's new value is " + newValue);
originalSet.call(this, newValue);
};
Object.defineProperty(MyClass.prototype, "a", desc);
// Use it after redefining
console.log(instance.a); // 2, after seeing console statement
instance.a = 3; // Triggers another console statement
console.log(instance.a); // 3 (w/ console statement)
The reason what you did didn't work was partially that you never set the new descriptor on the object. The descriptor you get back doesn't provide direct access to the definition within the object, it's just a new object created by getOwnPropertyDescriptor. To make changes effective, you need to set the new descriptor.
You asked below about doing the same for MyClass itself. As you pointed out, in addition to replacing the function, we need to be sure that its properties show up on the replacement for it.
A simple, easy way to do that is to make the new function inherit from the old:
const originalConstructor = MyClass;
MyClass = class MyClass extends originalConstructor {
constructor(...args) {
return new originalConstructor(...args);
}
};
It's a little-known fact that when B extends A, there are two things that happen:
B.prototype's prototype is set to A.prototype (this is commonly known), and
B's prototype is set to A (less well-known)
So any statics on A are available on B through inheritance.
So:
class Base {
feature() {}
static staticFeature() {}
}
class MyClass extends Base {
subFeature() {}
static staticSubFeature() {}
}
const originalConstructor = MyClass;
MyClass = class MyClass extends originalConstructor {
constructor(...args) {
return new originalConstructor(...args);
}
};
console.log(typeof MyClass.staticFeature);
console.log(typeof MyClass.staticSubFeature);
const instance = new MyClass();
console.log(typeof instance.feature);
console.log(typeof instance.subFeature);
Or if you want a really high-fidelity copy, you'd derive from MyClass's prototype and then copy over any MyClass own properties; but it's more complicated:
const originalConstructor = MyClass;
MyClass = class MyClass extends Object.getPrototypeOf(originalConstructor) {
constructor(...args) {
return new originalConstructor(...args);
}
};
Object.getOwnPropertyNames(originalConstructor)
.concat(Object.getOwnPropertySymbols(originalConstructor))
.forEach(name => {
MyClass[name] = originalConstructor[name];
});
Example:
class Base {
feature() {}
static staticFeature() {}
}
class MyClass extends Base {
subFeature() {}
static staticSubFeature() {}
}
const originalConstructor = MyClass;
MyClass = class MyClass extends Object.getPrototypeOf(originalConstructor) {
constructor(...args) {
return new originalConstructor(...args);
}
};
Object.getOwnPropertyNames(originalConstructor)
.concat(Object.getOwnPropertySymbols(originalConstructor))
.forEach(name => {
MyClass[name] = originalConstructor[name];
});
console.log(typeof MyClass.staticFeature);
console.log(typeof MyClass.staticSubFeature);
const instance = new MyClass();
console.log(typeof instance.feature);
console.log(typeof instance.subFeature);

How to implement instance counter in es6?

I was learning es6 so I was trying to convert these code from es5 to es6.
I knew how to make an instance counter in es5. The id built on A.prototype and counter built on A itself. When I built a instance by A, it will trigger counter++ to set id. Thus, it implements inheritance counter of action.
var A = (function () {
function A() {
this.id = ++A.counter;
console.log(this.id);
}
A.counter = 0;
return A;
}());
a = new A(); // 1
b = new A(); // 2
c = new A(); // 3
If I worked in es6, how do I implement same feature?
The other two answers are completely correct. But if you wanted to get super crazy ES6 you could make getters for the properties.
class A {
constructor() {
this._id = A.counter;
console.log(this.id);
}
get id() {
return this._id;
}
static get counter() {
A._counter = (A._counter || 0) + 1;
return A._counter;
}
}
a = new A() // <- 1
b = new A() // <- 2
c = new A() // <- 3
That way your counter and id are both read only, and your counter increments automatically every time you access it...plus it's all tidy inside the class definition instead of needing to go outside it.
Not saying you need to do it this way...but it seems like you're learning about ES6 and this example shows a couple of neat tricks about what you can do with it.
You'd do it in exactly the same way as in ES5:
class A {
constructor() {
this.id = ++A.counter;
console.log(this.id);
}
}
A.counter = 0;
var a = new A(); // 1
var b = new A(); // 2
var c = new A(); // 3
(you can also add the same unnecessary IIFE if you want)
The es6 class can complete you wanted.
id isn't built on A.prototype, but on each A instance. The constructor is to do a instance. So you can look, it does this.id = ++A.counter when class A is built a new instance.
class A {
constructor(){
this.id = ++A.counter;
console.log(this.id)
}
}
A.counter = 0;
a = new A()
b = new A()
c = new A()
Here is a solution which is also fully contained in an ES6 class as BryanGrezeszak's answer but with a different behavior that may be useful in some cases:
class A {
constructor() {
this.constructor.counter = (this.constructor.counter || 0) + 1;
this._id = this.constructor.counter;
console.log(this.id);
}
get id() {
return this._id;
}
}
class B extends A {}
class C extends A {}
a = new B(); // <- 1
b = new C(); // <- 1
c = new B(); // <- 2
d = new A(); // <- 1
The difference with this code is that inherited classes have their own independent counter.
If you don't want to add properties to the constructor, you can replace
this.constructor.counter
with
Object.getPrototypeOf(this).counter
that is the current way of expressing the deprecated
this.__proto__.counter
That way counter will be a static property of the class rather than a property of the (static function) constructor.

Passing object with predefined props to class constructor es6

I am trying to pass to my class constructor an object with predefined properties.
like this
class Test {
constructor({
a = "test a",
b = "test b"
}) {
}
}
P.S. I know how to define properties of objects. I want to know how to predefine properties.
It seems you want to pass one object to the constructor and have its properties assigned to single keys of the class. You can use destructuring for that this way:
class MyClass {
constructor({a,b} = {a:1,b:2}) {
this.a = a;
this.b = b;
}
}
Be aware that this is not safe for partially filled objects:
var instance = new MyClass({a:3});
// instance.b == undefined
Handling this could be done like so:
class MyClass {
constructor({a=1,b=2} = {}) {
this.a = a;
this.b = b;
}
}
Which results in:
var instance = new MyClass({a:3});
// instance.a == 3
// instance.b == 2
The simple solution would be to pass the constructor and object during instantiation:
class Test {
constructor(obj){
this.a = obj.a;
this.b = obj.b;
}
};
const obj = {
a: 'value',
b: 'value'
};
const newtTest = new Test(obj);
Like I said in my comment, there's an example right at the top of the documentation.
class Test {
constructor(/* put arguments here if you want to pass any */){
//Pre-define a and b for any new instance of class Test
this.a = "test a";
this.b = "test b";
}
}

Categories