Why I prefer the Pseudoclassical pattern for creating classes in JavaScript (and why you should too)...

First of all, I should acknowledge that there are programmers out there that insist that JavaScript has no such thing as classes, and in many ways they are justified. To set this matter aside, I would like to continue the article under the credence that a class may be interpreted as a construct that can produce many similar objects that share the same properties and/or methods.

To give a quick background about this specific discrepancy, this difference between Javascript and other languages is the inheritance pattern associated with the languages. While other object-oriented languages (e.g. Java or C++) have classical inheritance patterns, JavaScript has a prototypal inheritance pattern. This means that new instances of a class in Java will look up the class chain, while new instances of a ‘class’ in Javascript will lookup the prototype chain. The prototype chain is what gives JavaScript its power. We'll come back to this.

JavaScript’s Different Instantiation Patterns

There are a few ways that we can create classes in JS. However, not all of these patterns use JavaScript’s full potential. Here I will quickly explain the differences between the four patterns that I am aware of, and why I believe pseudoclassical is the most effective.

1) Functional

var Mammal = function(weight, age) {  
  var obj = {};
  obj.hasFur = true;
  obj.weight = weight;
  obj.age = age;
  obj.eat = function(foodWeight) {
    obj.weight+=foodWeight;
  };
  obj.smokes = function() {
    obj.age++;
  };
  return obj;
};

The Functional instantiation pattern contains all of the properties and methods of an object within the constructor function. This is probably the easiest to understand and therefore gets a lot of credit for being easy to read. Each call to the constructor function will create a new object, properties, and methods for every new instance of a class. e.g. saying...

var goat = Mammal(30, 4);  

...will set the variable goat equal to an object with a weight of 30 and an age of 4, with a hasFur property and eat and smokes methods. The goat object does not have a constructor property, so calling goat.constructor would look up the prototype chain and find the constructor value equal to the Object constructor function, since goat is merely referencing a standalone object. This is a completely legitimate way of creating a new instance of a Mammal, however we are creating duplicate methods each time we need to create a new instance. In addition, there is some crucial JS power that has yet to be wielded.

2) Functional-Shared

The Functional-Shared instantiation pattern takes away burden of writing out all the methods in the constructor function by simply extending the object with another object’s properties. The example below uses the jQuery extend method to add on the methods to the object within the constructor function. *NB* extend is not a native JS function!

var Mammal = function(weight, age) {  
  var obj = {};
  obj.hasFur = true;
  obj.weight = weight;
  obj.age = age;
  jQuery.extend(obj, mammalMethods);
  return obj;
};

var mammalMethods = {  
  eat: function(foodWeight) {
    this.weight+=foodWeight;
  },
  smokes: function() {
    this.age++;
  }
};

This works as well, though we haven’t really done much differently here other than storing the methods outside of the constructor function. Next, we’ll see where JavaScript’s strengths really lie.

3) Prototypal

So far we’ve just been creating a bunch of similar objects that all share the same properties and methods, and in that sense could be considered members of the same class. However, what if you wanted to create subclasses that created objects that inherited all of the properties and methods shared by the parent class? Shouldn’t all instances of a feline subclass also be able to inherit all of the same properties and methods of all mammals?

In the previous instantiation patterns, all instances of subclasses would need to recreate the properties and methods stored on its’ parent classes in order to inherit these properties. In other words, we had no way of being able to delegate failed property / method lookups to a parent class. This is where JavaScript’s prototype chain comes into play. If a property is not found on an object, the prototype chain is traversed to see whether or not any of the object’s prototypes contain that property. If the property is found, the result is passed back to the original object and returned as the value of that object’s property.

This is a simplified version of what is actually happening within the prototype chain, which I will expand upon a bit more in detail below. Since we are missing the delegation relationship that prototypal inheritance gives us in both Functional and Functional-shared patterns, we can use Object.create to build this desired delegation relationship. For example:

var Mammal = function(weight, age) {  
  var obj = Object.create(Mammal.prototype);
  obj.hasFur = true;
  obj.weight = weight;
  obj.age = age;
  return obj;
};

Mammal.prototype.eat = function(foodWeight) {  
  this.weight+=foodWeight;
};
Mammal.prototype.smokes = function() {  
  this.age++;
};

In fact, Object.create is the only way that you can create a delegation relationship in JavaScript! In the example above, within the Mammal constructor function we are creating a new object obj and telling it to delegate any failed property lookups on obj to the object stored on the prototype property on the Mammal constructor function (Mammal.prototype), which was created at the time that the Mammal function was declared.

So when a new instance of a Mammal is created, e.g...

var tiger = Mammal(200, 15);  

...JavaScript creates a new object that gets stored in the tiger variable, where the tiger’s __proto__ property directly refers to the object stored at the prototype property of tiger's constructor function. Therefore, we could say that the following is true:

>  console.log(tiger.__proto__ === tiger.constructor.prototype);
<  true  

The __proto__ property is what is referenced when looking up the prototype chain. With this knowledge, would you be able to figure out what is stored on the Mammal constructor function's __proto__ property?

If you screamed out “The Function prototype object!” you would be right. you.awesomePoints++; This is because Mammal is a function, and all functions delegate their property lookups to the Function.prototype.

The benefits of Prototypal inheritance are fascinating. It allows us to easily create classes that inherit from other classes (called subclasses) easily, and it also allows us keep our code DRY. But just when you thought this was as good as it got, JavaScript made it even simpler to build out "classes". Drumroll please...

4) Pseudoclassical

The closest JS gets to recreating an actual classical inheritance pattern is the aptly named pseudoclassical pattern. This basically does the same thing as the prototypal pattern, except we are able to prune our code even further, enhancing clarity and the DRY-ness of our code.

A Whole ‘new’ World

In order to alleviate the need to write out Object.create and return that object every time, adding the new keyword before a new instance of a class takes care of this for us. So writing...

var bear = new Mammal(400, 26);  

…is essentially inserting the following lines into the beginning and end of the Mammal constructor function…

//var Mammal = function(weight, age) {
    this = Object.create(Mammal.prototype);
    // …original function code here...
    return this;
//};

…so by creating a new instance of a class with the new keyword, we bind that instance to the this keyword. For more info on this, check out my post on What is the meaning of (JavaScript's) this? here!

Using the new keyword really frees up our constructor function. The completely refactored model looks like this...

var Mammal = function(weight, age) {  
  this.hasFur = true;
  this.weight = weight;
  this.age = age;
};

Mammal.prototype.eat = function(foodWeight) {  
  this.weight+=foodWeight;
};
Mammal.prototype.smokes = function() {  
  this.age++;
};

...where each this within the constructor function is a reference to a new instance of the Mammal class created with the new keyword. Instances of a class in the Pseudoclassical pattern must be created with the new keyword in order to be instantiated correctly.

Though this arguably has some functionality that is hidden from view, I believe that the Pseudoclassical pattern still remains easy to comprehend and is the clear favorite among the instantiation patterns in JavaScript.

Summary of the Benefits of the Pseudoclassical Instantiation Pattern
  • The Pseudoclassical pattern stores methods on the prototype object of the constructor function, eliminating the need to recreate methods for each instance of a class (vs. Functional and Functional-Shared).
  • The Pseudoclassical pattern eliminates the need to write out Object.create() and return an object to set equal to an instance of a class (vs. Prototypal).
  • The Pseudoclassical pattern makes use of the this keyword within the constructor function to keep the constructor function's code DRY and flexible (vs. all other patterns).

Hopefully, this should be enough evidence to persuade you of the benefits of becoming comfortable with using the Pseudoclassical pattern to create classes within JavaScript. It is also the most commonly used pattern among JS developers, so it would benefit you to have a good handle on it. Let's get Pseudoclassical!

comments powered by Disqus