Javascript Class Instantiation Patterns

Javascript

While technically there are no classes in Javascript, as an object oriented language we need techniques to create objects that model our data and keep our code base DRY. Enter class instantiation patterns!

Class instantiation in Javascript is primary done using the pseudoclassical style, which is a fine enough method, but can often lead to confusion as the pattern seemingly does not comply with Javascript conventions. By walking through a few other patterns before covering pseudoclassical, I hope to provide better understanding of how pseudoclassical instantiation works as well as greater perspective on other class construction options available.

For our code examples I'll be creating a dog class that has name and age properties and a bark method.

Functional Instantiation

We'll start with the functonal style which is perhaps the simplest and easiest to understand pattern. Those familiar with javascript will note that this pattern looks similar to creating a regular object, which is what makes it so easy to digest. Let's take a look at some code:

var dogClass = function(name) {
    var dogInstance = {};
    dogInstance.name = name;
    dogInstance.age = 0;
    dogInstance.bark = function() {
        console.log("Woof!");
    };
    
    return dogInstance;
}

We create a class function which contains all the logic for building our object instance. There is nothing fancy going on inside of our class function. We simply create an empty object, add our properties and methods and return the object. Here's how to create an instance using our constructor:

var dog = dogClass("Fido");

PROS:

  • Easy to use, easy to understand. Creates objects using a pattern familiar to all Javascript developers.
  • Only pattern that allows properties to be private through proper use of closure scope.

CONS:

  • Does not re-use or share functions. When an instance is created that instance's methods are unique to it. If the constructor methods change, existing instances will keep the outdated methods.
  • Greater time and space complexity as each time an instance is created we also must create a new set of methods.

Functional Shared Instantiation

The functional-shared pattern shares a similar structure to functional, but rather than declare the class's methods inside the constructor function, we declare them outside:

var dogClass = function(name) {
  var dogInstance = {};
  dogInstance.name = name;
  dogInstance.age = 0;
  _.extend(dogInstance, dogClassMethods);
  
  return dogInstance;
};

var dogClassMethods = {};
dogClassMethods.bark = function() {
  console.log("Woof!");
};

Now instead of creating an object, adding properties and methods and returning the object, we create a dogClassMethods object to store all of the class objects. We then use the extend method to apply the dogClassMethods methods to the dogInstance. The _.extend method used here comes from the underscore.js library and simply adds the methods of the second argument to the object in the first argument.

PROS:
Simple pattern for creating shared methods among objects.

CONS:
Once an object instance is created, it retains access to the methods that were extended to it. However if the class methods change, the instance will need to be extended the new methods.

Prototypal Instantiation

Our third patterns looks nearly identical to the functional-shared pattern with one notable difference:

var dogClass = function(name) {
  var dogInstance = Object.create(dogClass.prototype);
  dogInstance.name = name;
  dogInstance.age = 0;
  
  return dogInstance;
};

dogClass.prototype.bark = function() {
  console.log("Woof!");
};

Here Object.create sets a reference on the object instance to the argument's prototype object, which we have modified to include the desired methods. By doing so, we provide the object instance access to the argument object's protoype methods.

PROS:
Object instances retain reference to their prototype object. Whenever the prototype is updated, all instances will be updated as well. This makes maintaining instances a breeze.

CONS:
The prototype object can be confusing. It is not a property or constructor function. Rather it is an object that is used to store properties and methods.

Pseudoclassical Pattern

Our final class pattern is also the one you're likely to encounter most often in Javascript. Very similar to prototypal, psuedoclassical abstracts out a few items to create a clean and concise constructor function:

var Dog = function(name) {
  this.name = name;
  this.age = 0;
};

Dog.prototype.bark = function() {
  console.log("Woof!");
};

As you can see this code looks drastically different from where we started and therein lies much of the confusion with the pseudoclassical class constructor.

The most important note on this pattern is that two important statements have been abstracted. The first line of the constructor function has been removed

Let's add the statments in using comments and see how the code looks:

var Dog = function(name) {
  // this = Object.create(Dog.prototype);
  this.name = name;
  this.age = 0;
  // return this;
}
Dog.prototype.bark = function() {
  console.log("Woof!");
};

Look familiar?! Yep, pseudoclassical is basically just an abstracted version of prototypal. The context of the function is explicitly bound to the newly created object, which has reference to the class's prototype. After finishing constructing the object's properties we return the object instance (just like in all the other patterns we've examined).

Why abstract out lines like this? Good question. When Javascript was being developed Java was the dominant language in the industry and there was demand for a simple class creation function that utilized the "new" keyword. Hence the pseudoclassical pattern was born and is still today by far the most commonly used class instantion method.

To create an instance from the example above, we simply use:

var dog = new Dog("Beethoven");

PROS:
Concise.
Object instances reference the object prototype, so changes to the prototype object are reflected in every instance.
Clear intention. The "new" keyword syntax makes it abundantly clear that a new instance is being created.

CONS:
As discussed this pattern is often misunderstood as the abstracted lines make the function appear to defy the rules of Javascript.

Conclusion

So which pattern is best to use? This is really a matter of preference. Each pattern has its merits and will likely get the job done in most cases. Pseudoclassical is the dominant industry choice, so using this pattern will comply most often with convention.

In theory, I prefer prototypal as it most accurately expresses the process of object creation. In practice, I typically use pseudoclassical as it's conciseness combined with use of the "new" keyword makes it both easy to maintain and read (provided your team has a firm grasp on what's actually going on behind the scenes!). I also like using the functional pattern when a small amount of instances will be created. I enjoy the simplicity of the pattern along with it's ability to keep properties private.

By walking through the various class instantiation patterns we gain a greater understanding of how each method works. I hope you now have a thorough understanding of the pseudoclassical style, the methodology behind it and insight into other instantiation patterns available.