Grégory NEUT Grégory NEUT - 1 month ago 7
Node.js Question

Singleton Inheritance Buggy Behavior

I have spotted buggy behavior in javascript es6 inheritance using Singleton pattern.

Code is:

let instanceOne = null;

class One {
constructor() {
if (instanceOne) return instanceOne;

this.name = 'one';
instanceOne = this;
return instanceOne;
}

method() {
console.log('Method in one');
}
}


let instanceTwo = null;

class Two extends One {
constructor() {
super();

if (instanceTwo) return instanceTwo;

this.name = 'two';
instanceTwo = this;
return instanceTwo;
}

method() {
console.log('Method in two');
}
}

const objOne = new One();
const objTwo = new Two();

console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();


Display is:

two
two
Method in one
Method in one


The inheritance get fucked up somehow. Here the attributes get overridden but not the object methods.

My question is why is it working (like now throw) and can you explain this behavior?

It appears that new objects need brand new object as parent (see solution below).




If you encounter the same problem, here is my solution:

let instanceOne = null;

class One {
constructor(brandNewInstance = false) {
if (instanceOne && !brandNewInstance) return instanceOne;

this.name = 'one';

if (brandNewInstance) return this;

instanceOne = this;
return instanceOne;
}

method() {
console.log('Method in one');
}
}


let instanceTwo = null;

class Two extends One {
constructor() {
super(true);

if (instanceTwo) return instanceTwo;

this.name = 'two';
instanceTwo = this;
return instanceTwo;
}

method() {
console.log('Method in two');
}
}





I use node.js v6.9.1

Answer

You are doing something a bit strange. Constructors and subclasses in ecmascript 6 do not work in the way you think they do. You may wish to read this blog post (particularly section 4) to learn more.

Taking from that article, your code looks like this under the hood:

let instanceOne = null;

function One() {
//  var this = Object.create(new.target.prototype);  // under the hood

    if (instanceOne) return instanceOne;

    this.name = 'one';
    instanceOne = this;
    return instanceOne;
}
One.prototype.method = function() { console.log('Method in one'); }

let instanceTwo = null;

function Two() {
    var that = undefined;

    that = Reflect.construct(One, [], new.target);

    if (instanceTwo) return instanceTwo;

    that.name = 'two';
    instanceTwo = that;
    return instanceTwo;
}
Two.prototype.method = function() { console.log('Method in two'); }
Object.setPrototypeOf(Two, One);
Object.setPrototypeOf(Two.prototype, One.prototype);

const objOne = Reflect.construct(One, [], One);
const objTwo = Reflect.construct(Two, [], Two);

console.log(objOne.name);
console.log(objTwo.name);
objOne.method();
objTwo.method();

(new.target is the value passed as the third argument of Reflect.construct)

You can see that for the Two class, no new object is being created and Two.prototype is not used. Instead, the One singleton instance is used and mutated.