Instantiation patterns

20 Oct 2019

The difference between an object decorator and a class is only that a class creates the object that it is augmenting.

Let me be clear- a class is any construct capable of creating many similar objects that all conform to an interface. Because JavaScript is such a flexible language, there are many ways to make classes. We will cover four of the most common ones:

Functional Class Pattern:

function Car(model) {
  var car = {};
  car.position = 0;
  car.model = model;
  car.move = function move() { car.position += 1 };

  return car;
}

Pros: very simple, readable, and speaks for itself.
Cons: duplication of methods for each instance. This is only a concern when thousands and thousands of instances will be created in an application which is usually not the case, and typically well worth the simplicity if you ask me

Functional Shared Class Pattern:

function Car(model) {
  var car = {};
  car.model = model;
  Object.assign(car, CarMethods)
  return car;
}

CarMethods = {
  move: function(){ this.position += 1; },
  honk: function(){ console.log("Beep!"); };
}

Pros: reusing the same methods for each instance.
Cons: using keyword ‘this’ in our methods can be confusing and cause problems depending on how the method is called. Also, Object.assign is an ES6 feature.

Prototypal Class Pattern:

This pattern takes advantage of instances delegating up the prototypal chain. For instance: (that was a code joke in case you missed it)

function Car(model) {
  var car = Object.create(CarMethods);
  car.model = model;                    
  return car;
}

CarMethods = {
  move: function(){ this.position += 1; },
  honk: function(){ console.log("Beep!"); };
}

Here, each instance of Car delegates failed lookups to CarMethods.

Pros- We can take advantage of Prototype chains, and even mask methods higher in the chain. Cons- using keyword ‘this’ in our methods can be confusing and cause problems depending on how the method is called.

Pseudoclassical Class Pattern:

function Car(model) {
  this.model = model;
}

  Car.prototype.move: function(){ this.position += 1; },
  Car.prototype.honk: function(){ console.log("Beep!"); };

When the new keyword is used to create a new instance of Car e.g. var ford = new Car('ford') it runs the function in “constructor mode” which means that the interpreter implicitly changes the code to look like this:

function Car(model) {
  this = Object.create(Car.prototype);
  this.model = model;
  return this;
}

  Car.prototype.move: function(){ this.position += 1; },
  Car.prototype.honk: function(){ console.log("Beep!"); };

Fundamentally, this is no different from the prototypal pattern. We are simply substituting instances’ delegation to the CarMethods object, to the Car.prototype object.

Now, you may be wondering, why we did not create Car.prototype = {}. That’s because each new function ships with an empty .prototype property that is intended for storing methods. NOTE: There is nothing special about this .prototype property. A lookup of Car.move or Car.honk would give us undefined. Car DOES NOT have any sort of special delegation to it’s own prototype property- like all other functions, it delegates failed property lookups where all functions delegate failed property lookups - Function.prototype

Pros- We can take advantage of Prototype chains and Object delegation.
Cons- The awful naming convention of prototype can lead people to believe that there is something special about a function’s .prototype property, when really there isn’t. Also, using keyword ‘this’ in our methods can be confusing and cause problems depending on how the method is called.