JavaScript inheritance patterns

An overview and comparison

Image for post
Image for post

JavaScript is a very powerful language. So powerful, in fact, that there are multiple different ways of designing prototypes and instantiating objects. There are tradeoffs when using each different method, and I aim to assist newcomers to the language by clearing up the mess. This is a follow-up to my previous post, Stop Classifying JavaScript. I received many comments and responses asking for code examples, so here they are.

JavaScript has Prototypal Inheritance

This means that, in JavaScript, objects inherit from other objects. Basic objects in JavaScript, created with the curly braces, have only one prototype: is, in itself, an object, and all members of are accessible from all objects.

Basic arrays, created with the square brackets, have multiple prototypes, including and This means that all members of and all members of are accessible as members of arrays. Any members that overlap, like and are overridden by the closest prototype, in this case.

Prototype definitions and object instantiation

Method 1: Constructor pattern

JavaScript has a special type of function called constructor functions, which act similarly to constructors in other languages. The are called mandatorily with the keyword and bind the keyword to the object being created by the constructor function. A typical constructor may look like this:

function Animal(type){
this.type = type;
}
Animal.isAnimal = function(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type : true;
};
function Dog(name, breed){
Animal.call(this, "dog");
this.name = name;
this.breed = breed;
}
Object.setPrototypeOf(Dog.prototype, Animal.prototype);
Dog.prototype.bark = function(){
console.log("ruff, ruff");
};
Dog.prototype.print = function(){
console.log("The dog " + this.name + " is a " + this.breed);
};
Dog.isDog = function(obj){
return Animal.isAnimal(obj, "dog");
};

The usage of this constructor looks like instantiation in other languages:

var sparkie = new Dog("Sparkie", "Border Collie");sparkie.name;    // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true

and are prototype methods which apply to all dogs. The and properties are own properties that are set by the constructor. Usually, all methods are set in the prototype and all properties are set by the constructor.

Method 2: ES2015 (ES6) Class definitions

has been a reserved keyword in JavaScript since the beginning, and now there is finally a use for it. Class definitions in JavaScript look a lot like they do in other languages.

class Animal {
constructor(type){
this.type = type;
}
static isAnimal(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type : true;
}
}
class Dog extends Animal {
constructor(name, breed){
super("dog");
this.name = name;
this.breed = breed;
}
bark(){
console.log("ruff, ruff");
}
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
static isDog(obj){
return Animal.isAnimal(obj, "dog");
}
}

A lot of people like this syntax because it combines the constructor, static, and the prototype method declarations into one nice block. The usage is exactly the same as the Constructor method.

var sparkie = new Dog("Sparkie", "Border Collie");

Method 3: Explicit prototype declaration, Object.create, method factory

This method really displays the prototypal inheritance behind the workings of the syntax, and allows for the omittance of the keyword.

var Animal = {
create(type){
var animal = Object.create(Animal.prototype);
animal.type = type;
return animal;
},
isAnimal(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type : true;
},
prototype: {}
};
var Dog = {
create(name, breed){
var dog = Object.create(Dog.prototype);
Object.assign(dog, Animal.create("dog"));
dog.name = name;
dog.breed = breed;
return dog;
},
isDog(obj){
return Animal.isAnimal(obj, "dog");
},
prototype: {
bark(){
console.log("ruff, ruff");
},
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
}
};
Object.setPrototypeOf(Dog.prototype, Animal.prototype);

This syntax is nice because the prototypes are very explicitly defined. It is very clear exactly which are members of the prototype and which are members of the object. is nice because it allows the creation of an object with a specific prototype. The check still works in both cases. The usage is different, but not incredible different:

var sparkie = Dog.create("Sparkie", "Border Collie");sparkie.name;    // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true

Method 4: Object.create, top-level factory, prototype post-declaration

This method is a slight variation of Method 3, where the factory is the class, versus the class being an object with a factory method. It looks like the constructor example (Method 1), but uses factories and instead.

function Animal(type){
var animal = Object.create(Animal.prototype);
animal.type = type;
return animal;
}
Animal.isAnimal = function(obj, type){
if(!Animal.prototype.isPrototypeOf(obj)){
return false;
}
return type ? obj.type === type : true;
};
Animal.prototype = {};
function Dog(name, breed){
var dog = Object.create(Dog.prototype);
Object.assign(dog, Animal("dog"));
dog.name = name;
dog.breed = breed;
return dog;
}
Dog.isDog = function(obj){
return Animal.isAnimal(obj, "dog");
};
Dog.prototype = {
bark(){
console.log("ruff, ruff");
},
print(){
console.log("The dog " + this.name + " is a " + this.breed);
}
};
Object.setPrototypeOf(Dog.prototype, Animal.prototype);

This method is nice because it’s usage looks like Method 1, but does not require the keyword and works with The usage is the same as the first method, but without the

var sparkie = Dog("Sparkie", "Border Collie");sparkie.name;    // "Sparkie"
sparkie.breed; // "Border Collie"
sparkie.bark(); // console: "ruff, ruff"
sparkie.print(); // console: "The dog Sparkie is a Border Collie"
Dog.isDog(sparkie); // true

Comparison

Method 1 vs Method 4

There is very little reason to use Method 1 over Method 4. Method 1 requires either the use of in your code or a check like this in the constructor:

if(!(this instanceof Foo)){ 
return new Foo(a, b, c);
}

At which point you may as well just use in a factory. You also can’t use or on constructor functions, because they mess up . The check above could remedy that issue as well, but if you want to use an unknown amount of arguments, you have to use a factory.

Method 2 vs Method 3

The same arguments about constructors and that applied above apply to this as well. The check is necessary for using the syntax without or with and .

My opinion

A programmer should strive for code clarity. Method 3's explicit syntax very clearly shows exactly what is going on. It also allows for easy multiple inheritance and concatenative inheritance. Since using the keyword violates the open-closed principle due to incompatibility with or it should be avoided. The keyword hides the prototypal nature of JavaScript’s inheritance behind the guise of a classical system.

is more expressive and clearer than a bound variable and . Also, the prototype is stored in an object possibly outside the scope of the factory itself, so it can be modified and improved more easily and with syntax, just like ES6 classes.

Adding something which is unnecessary, possibly damaging, and counter to the very nature of the language is a bad move.

If you choose to use I hope I never have to try to maintain your code. In my opinion, developers should avoid the use of constructorsand and use methods of inheritance which more closely follow the language architecture.

Glossary

copies all enumerable properties of object onto object and then returns object

creates a new bare object with the prototype

sets the internal property of to

Written by

I'm just a college kid who enjoys programming and some other stuff

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store