Pseudoclassical Inheritance


Also sometimes referred to as Constructor Inheritance, Pseudoclassical Inheritance uses constructors to mimic classical inheritance as found in object oriented languages such as Java. In it’s most standard form Pseudoclassical Inheritance uses the following techniques:

  1. Inheritance between the constructors’ prototype properties is established via Object.create().
  2. Initialization logic is replicated through constructor stealing (if needed).

NB: prototypal inheritance is more powerful than classical inheritance in the sense that classical inheritance can be implemented with a prototypal model (while the opposite does not hold).


Inheritance

Inheritance is established between the constructors’ prototype properties via Object.create(), like so:

function Vertebrate() {}

Object.assing(Vertebrate.prototype, {
    greet() {
        console.log(`Hi! I'm a vertebrate!`);
    },
});

function Bird() {}

// Setting up inheritance ••••••••••••••••••••••••••••••••••••
// Style 1 ---------------------------------------------------
Bird.prototype = Object.create(Vertebrate.prototype, {
    constructor: {
        value: Bird,
        writable: true,
        configurable: true,
        // enumerable is set to false by default
    },
});
// Style 2 ---------------------------------------------------
Object.setPrototypeOf(Bird.prototype, Vertebrate.prototype);
// -----------------------------------------------------------

const dodo = new Bird();

dodo.greet();   // Hi! I'm a vertebrate!

Style 2 is more concise and clearer in its intention. Note that although Object.setPrototypeOf() is discouraged for performance reasons, the above kind of use case is probably fine.

Caveat

It is very common to find inheritance being established in the following problematic way instead:

Bird.prototype = new Vertebrate();
// Sometimes followed by:
Bird.prototype.constructor = Bird;

This can lead to unintended consequences as:

  • Establishing inheritance through a constructor invocation:

    • requires the constructor to be callable without any arguments.
    • may produce unexpected side effects.
  • Simply reassigning the constructor property will make it enumerable, while it is usually expected to be non-enumerable (this can become problematic for example when subsequently performing mixins via Object.assign(), which may then also pick up and overwrite constructor properties).

Initialization

If required, the initialization logic can be replicated in the subtype through constructor stealing, like so:

function Vertebrate(name) {
    this.name = name;
}

function Bird(name, canFly) {
    Vertebrate.call(this, name);    // Constructor stealing
    Object.assign(this, { canFly });
}

const dodo = new Bird("Dodo", false);

console.log(dodo.name);     // Dodo
console.log(dodo.canFly);   // false

Putting Everything Together

Putting everything together, the most standard and accurate implementation of Pseudoclassical Inheritance will follow the pattern below:

function Vertebrate(name) {
    this.name = name;
}
Object.assign(Vertebrate.prototype, {
    greet() {
        console.log(`Hi! I'm a ${this.name}!`);
    },
});

function Bird(name, canFly) {
    Vertebrate.call(this, name);    // Constructor stealing
    Object.assign(this, { canFly });
}
Object.setPrototypeOf(Bird.prototype, Vertebrate.prototype);

const dodo = new Bird("Dodo", false);

dodo.greet();               // Hi! I'm a Dodo!
console.log(dodo.name);     // Dodo
console.log(dodo.canFly);   // false

Node’s util.inherits()

The now discouraged util.inherits(constructor, superConstructor) method in Node.js could also be used to establish inheritance between the constructors’ prototype properties. It further makes the superConstructor available on the constructor via constructor.super_. Behind the scenes the method is implemented as follows:

function inherits(constructor, superConstructor) {
    Object.defineProperty(constructor, "super_", {
        value: superConstructor,
        writable: true,
        configurable: true,
    });
    Object.setPrototypeOf(constructor.prototype, superConstructor.prototype);
}

Resources