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:
- Inheritance between the constructors’
prototype
properties is established viaObject.create()
. - 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 viaObject.assign()
, which may then also pick up and overwriteconstructor
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
- Chapter 5 - Inheritance, The Principles of Object-Oriented JavaScript, Zakas (2014).
- Chapter 5 - Inheritance, JavaScript: The Good Parts, Crockford (2008).