WT

JavaScript Class Instantiation & Inheritance Patterns

At first glance, JavaScript may not seem like an object oriented programming (OOP) language, but it does have powerful object oriented programming capabilities. OOP features such as inheritance, polymorphism, and encapsulation are all enabled in JavaScript. However, being a very flexible language, there are several different ways to define classes and use them. Here’s an overview of all the different JavaScript class instantiation and inheritance patterns and how to implement them.

Sections

Functional

A class is a set of characteristics that define a collection of things. Knowing that something belongs to a certain class should indicate that certain properties are exhibited by that thing. In OOP, the “thing” would be referred to as an object, which is an instance of a class. The simplest way to create objects that all exhibit the same properties, is through a function.

Consider the function makeAnimal that takes a name and a color and returns animal objects. Every invocation of makeAnimal will create an instance that has name and color properties. This function is a constructor in the sense that it creates objects of a particular class.

var makeAnimal = function(name, color) {
  var animal = {};
  animal.name = name;
  animal.color = color;
  return animal
}

var bob = makeAnimal('bob', 'blue'); // bob.name === 'bob'
var yo  = makeAnimal('yo',  'yellow'); // yo.color === 'yellow'

Methods (functions associated with a class) can also be added to each instance via the constructor function.

var makeAnimal = function(name, color) {
  var animal = {};
  animal.name = name;
  animal.color = color;
  animal.growl = function() {
    console.log('grrr');
  }
  return animal
}

var bob = makeAnimal('bob', 'blue'); // bob.name === 'bob'

bob.growl() // logs 'grrr'

However, not all animals are capable of growling, so it may not be appropriate to add that method on all animals. This is a good reason to subclass. Subclassing under the functional pattern is very straight forward. I can create a function that makes bears, employing makeAnimal in the process:

var makeAnimal = function(name, color) {
  var animal = {};
  animal.name = name;
  animal.color = color;
  return animal;
}

var makeBear = function(name, color, type) {
  var bear = makeAnimal(name, color);
  bear.type = type;
  bear.growl = function() {
    console.log('grrr');
  }
  return bear;
}

var yogi = makeBear('yogi', 'brown', 'thief');

yogi.growl(); // logs 'grrr'

This, in essense, is inheritance under the functional pattern. You would be correct to point out that this does not provide certain capabilities offered by inheritance in other OOP languages. For instance, there is no way to know that bears made by makeBear are animals without examining the code. This code is also not very efficient. Every instance of bear (and animal, for that matter) holds properties that point to its own function definitions. There is no convenient method to alter the functionality of the entire class.

var yogi = makeBear('yogi', 'brown', 'thief');
var pooh = makeBear('winnie', 'yellow', 'honey');

yogi.growl(); // logs 'grrr'
yogi.growl === pooh.growl; // false

If my app required the creation of thousands of bears, there would be a lot of redundant memory costs associated with this pattern.

There are benefits to using this pattern though - this code is very transparent and easy to understand. Depending on the expected use of this function, the redundant memory costs may be perfectly acceptable.

Functional Shared

A slightly improved version of the functional pattern involves eliminating that redundant memory cost mentioned above. Instead of defining a function on each instance of the class, the methods can be defined in some form of method holder for each instance to point at.

Using the same example as above:

var makeAnimal = function(name, color) { // no change to this
  var animal = {};
  animal.name = name;
  animal.color = color;
  return animal;
}

var bearMethods = { // the holder of all methods that bears should have
  growl: function() {
    console.log('grrr');
  }
}

var makeBear = function(name, color, type) {
  var bear = makeAnimal(name, color);
  bear.type = type;
  bear.growl = bearMethods.growl; // point at same function as bearMethods.growl
  return bear;
}

var yogi = makeBear('yogi', 'brown', 'thief');
var pooh = makeBear('winnie', 'yellow', 'honey');

yogi.growl(); // logs 'grrr'
yogi.growl === pooh.growl; // true

If more methods are needed for bears, simply define them within bearMethods, and use a function such as extend within makeBear to extend all the methods onto the bear instance.

Inheritance would work the same way as the plain Functional pattern. A subclass of bears would invoke makeBear to create a bear (which extends all methods from bearMethods) and then decorate the instance with additional properties and methods specific to the subclass (for example, extending it with methods from a method holder called polarBearMethods).

Aside from the potential memory cost savings, this pattern does not offer many other benefits over the first functional pattern. There is still no convenient way to alter the methods for all instances of a class. Altering the bearMethods object will only point the properties at new function objects. The bears that have already been instantiated will not receive the update, unless explicitly updated with these new method definitions.

Prototypal

The functional patterns do not create proper objects in the classical sense. They do not allow inheritance, since the instances of the “class” are very much their own object with access to only their own properties. The prototypal pattern allows for this behavior through an object’s prototype.

Consider this example using pokemon under the functional shared pattern:

// functional shared pattern
var pokemonMethods = {
  growl: function() {
    console.log( this.name + '! ' + this.name + '!');
  }
}

var makePokemon = function(name, level, type) {
  var pokemon = {};
  pokemon.name = name;
  pokemon.level = level;
  pokemon.type = type;
  pokemon.growl = pokemonMethods.growl;
  return pokemon;
}

var zeni = makePokemon('squirtle', 15, 'water');
zeni.growl(); // logs 'squirtle! squirtle!'

pokemonMethods.fight = function() {
  console.log( this.name + 'used tackle!');
}

zeni.fight(); // fight is undefined!

A simple modification to this code transforms this into the prototypal instantiation pattern:

// prototypal pattern
var pokemonMethods = {
  growl: function() {
    console.log( this.name + '! ' + this.name + '!');
  }
}

var makePokemon = function(name, level, type) {
  var pokemon = Object.create(pokemonMethods); // *
  pokemon.name = name;
  pokemon.level = level;
  pokemon.type = type;
  // pokemon.growl = pokemonMethods.growl; // *
  return pokemon;
}

var zeni = makePokemon('squirtle', 15, 'water');
zeni.growl(); // logs 'squirtle! squirtle!'

pokemonMethods.fight = function() {
  console.log( this.name + ' used tackle!');
}

zeni.fight(); // logs 'squirtle used tackle!''

Note the very important difference within the makePokemon function. Instead of creating an empty object in the first line of makePokemon, Object.create is called with pokemonMethods as an argument. Object.create (introduced with ES5) is a method that returns a new object that will delegates failed property lookups to the object passed in to the function. For this reason, extending methods from the method holder to the instance is no longer necessary. Calling zeni.growl() will do the following:

  1. Search zeni for the method growl
  2. Fail method lookup on zeni
  3. Search pokemonMethods for the method growl
  4. Successful method lookup
  5. Invoke growl, with this referring to zeni

The most powerful aspect of this pattern is that new methods declared on pokemonMethods (the prototype) will be available to zeni without any additional action. Modifications to pokemonMethods will affect all those that inherit from it, so long as they rely on pokemonMethods for lookups. If a growl method got defined on zeni, it would effectively mask the growl method on the prototype.

zeni.growl = function() {
  console.log('squirrrrrrtttlleeeeee!!!');
}

zeni.growl(); // logs 'squirrrrrrtttlleeeeee!!!'

Inheritance under this pattern is also possible.

var squirtleMethods = Object.create(pokemonMethods); // *
squirtleMethods.fight = function() {
  console.log( this.name + ' used Water Gun!');
}

var makeSquirtle = function(level, color) {
  var squirtle = Object.create(squirtleMethods);
  squirtle.name = 'squirtle';
  squirtle.level = level;
  squirtle.type = 'water'
  squirtle.color = color; // new property for squirtle class
  return squirtle;
}

var zeni = makeSquirtle(15, 'blue');

zeni.growl(); // logs 'squirtle! squirtle!'
zeni.fight(); // logs 'squirtle used Water Gun!'

Note that growl is not a method available on squirtleMethods, but since squirtleMethods delegates to pokemonMethods, it is still available on zeni.

Pseudoclassical

The pseudoclassical pattern is the pattern that most resembles the classic object instantiation pattern of other langauges such as Java and C++. Here the function Pokemon is capitalized by convention to indicate that it is a constructor (the capitalization does nothing special besides indicate to readers that this is a constructor). Constructor functions need to be instantiated by using the new keyword.

The new keyword has the effect of letting this within the constructor function inherit from the prototype. The prototype in this case is Pokemon.prototype instead of a pokemonMethods. Pokemon.prototype is nothing more than an an object defined as a property of the constructor function (recall that functions are objects themselves, and can have arbitrary properties defined on them). For convenience, the prototype property is defined for you by JavaScript and is automatically the prototype of all instances created by the constructor.

var Pokemon = function(name, level, type) {
  this.name = name;
  this.level = level;
  this.type = type;
}

Pokemon.prototype.growl = function() {
  console.log( this.name + '! ' + this.name + '!');
}

Pokemon.prototype.fight = function() {
  console.log( this.name + ' used tackle!');
}

The pseudoclassical pattern really shines when it comes to inheritance. Here is an example of subclassing the Pokemon class to a Squirtle class.

var Squirtle = function(level, color) {
  Pokemon.call(this, 'squirtle', level, 'water'); // *
  this.color = color;
}

Squirtle.prototype = Object.create(Pokemon.prototype); // *
Squirtle.prototype.constructor = Squirtle; // *

Squirtle.prototype.fight = function() {
  console.log( this.name + ' used Water Gun!');
}

var zeni = new Squirtle(15, 'blue');

zeni.growl(); // logs 'squirtle! squirtle!'
zeni.fight(); // logs 'squirtle used Water Gun!'

There are a couple of important things to note about subclassing. The first line within the Squirtle constructor merely invokes the Pokemon constructor and passes along the appropriate arguments. The this keyword, which references the instance of Squirtle being created, will have properties name, level, and type assigned via Pokemon.

More important are the lines that set up the proper prototype chain. Without these two lines, Squirtle would merely be a regular constructor function, where instances of Squirtle would delegate failed property lookups to Squirtle.prototype. As mentioned before, Object.create returns an object that inherits from the prototype passed in as the argument. Using the code below, Squirtle.prototype now inherits from Pokemon.prototype. The growl method from Pokemon.prototype can now be called from zeni:

Squirtle.prototype = Object.create(Pokemon.prototype);

Something I have not mentioned earlier is that each prototype property of a function has another property constructor that points back at the function itself. That is:

Pokemon.prototype.constructor === Pokemon; // true

So what happens when we set Squirtle.prototype to Object.create(Pokemon.prototype)? Squirtle.prototype actually does not have its own constructor property (Object.create does not conveniently set this up for you). As a result, it delegates failed lookups to Pokemon.prototype, which does have a constructor property…pointing to Pokemon. This line is required to set things back to normal.

Squirtle.prototype.constructor = Squirtle;

// and now this makes sense
zeni.constructor === Squirtle; // true

The pseudoclassical instantiation pattern is usually the most popular. However, it requires a thorough understanding of the pseudoclassical instantiation pattern. Furthermore, before ES5 introduced Object.create there were several other conventions used to substitute this LOC, such as setting it equal to a new instance of Pokemon.

Squirtle.prototype = Object.create(Pokemon.prototype); // correct
Squirtle.prototype = new Pokemon(); // used to be common practice, not correct

Failed method lookups will fall through to the instance of Pokemon, and then to the prototype of Pokemon, as desired. However, this created problems if the Pokemon constructor required a lot of initialization parameters and could not accept undefined ones.

Know that Object.create is the only correct pattern to use, but there might still be established code bases out there with the old pattern. In fact, I often see blog posts and websites reference the old pattern as the proper way to set up inheritance in JS.

Summary

Even though the pseudoclassical pattern is the most powerful and is often optimized by many runtime engines, it may sometimes be benefiicial to go with a functional pattern for simplicity and transparency. It all depends on the intended usage. In my personal opinion, the functional and pseudoclassical patterns provide the best value. Here’s a summary:

  • Functional
    • Most transparent and easy to understand
    • Higher memory cost because each instance retains its own properties
  • Functional shared
    • Offers memory cost advantage over functional
    • Adds a little complexity over functional
  • Prototypal
    • Not much easier to comprehend compared to the pseudoclassical pattern
    • Does not offer any obvious advantages over pseudoclassical pattern
  • Pseudoclassical
    • Often the most optimized pattern
    • More complex, but is a very common pattern