Search
Close this search box.

JavaScript Class Patterns

To write object-oriented programs we need objects, and likely lots of them.

JavaScript makes it easy to create objects:

var liam = { name : "Liam", age : Number.MAX_VALUE };

But JavaScript does not provide an easy way to create similar objects. Most object-oriented languages include the idea of a class, which is a template for creating objects of the same type. From one class many similar objects can be instantiated.

Many patterns have been proposed to address the absence of a class concept in JavaScript. This post will compare and contrast the most significant of them.

Simple Constructor Functions

Classes may be missing but JavaScript does support special constructor functions. By prefixing a call to a constructor function with the ‘new’ keyword we can tell the JavaScript runtime that we want the function to behave like a constructor and instantiate a new object containing the members defined by that function. Within a constructor function the ‘this’ keyword references the new object being created –  so a basic constructor function might be:

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.toString = function() {
    return this.name + " is " + age + " years old.";
  };
}

var john = new Person("John Galt", 50);

console.log(john.toString());

Note that by convention the name of a constructor function is always written in Pascal Case (the first letter of each word is capital). This is to distinguish between constructor functions and other functions. It is important that constructor functions be called with the ‘new’ keyword and that not constructor functions are not.

There are two problems with the pattern constructor function pattern shown above:

  1. It makes inheritance difficult
  2. The toString() function is redefined for each new object created by the Person constructor. This is sub-optimal because the function should be shared between all of the instances of the Person type.

Constructor Functions with a Prototype

JavaScript functions have a special property called prototype. When an object is created by calling a JavaScript constructor all of the properties of the constructor’s prototype become available to the new object. In this way many Person objects can be created that can access the same prototype. An improved version of the above example can be written:

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = {
  toString: function() {
    return this.name + " is " + this.age + " years old.";
  }
};

var john = new Person("John Galt", 50);

console.log(john.toString());

In this version a single instance of the toString() function will now be shared between all Person objects.

Private Members

The short version is: there aren’t any. If a variable is defined, with the var keyword, within the constructor function then its scope is that function. Other functions defined within the constructor function will be able to access the private variable, but anything defined outside the constructor (such as functions on the prototype property) won’t have access to the private variable. Any variables defined on the constructor are automatically public. Some people solve this problem by prefixing properties with an underscore and then not calling those properties by convention.

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype = { _getName : function() { return this.name;
}
, toString : function() {
  return this._getName() + " is " + this.age + " years old.";
}
}
;

var john = new Person("John Galt", 50);

console.log(john.toString());

Note that the _getName() function is only private by convention – it is in fact a public function.

Functional Object Construction

Because of the weirdness involved in using constructor functions some JavaScript developers prefer to eschew them completely. They theorize that it is better to work with JavaScript’s functional nature than to try and force it to behave like a traditional class-oriented language. When using the functional approach objects are created by returning them from a factory function. An excellent side effect of this pattern is that variables defined with the factory function are accessible to the new object (due to closure) but are inaccessible from anywhere else. The Person example implemented using the functional object construction pattern is:

var john = new Person("John Galt", 50);
console.log(john.toString());

var personFactory = function(name, age) {
  var privateVar = 7;
  return {
    toString: function() {
      return name + " is " + age * privateVar / privateVar + " years old.";
    }
  };
};

var john2 = personFactory("John Lennon", 40);
console.log(john2.toString());

Note that the ‘new’ keyword is not used for this pattern, and that the toString() function has access to the name, age and privateVar variables because of closure.

Inheritance

Both of the above patterns can support inheritance but for now, favour composition over inheritance.

Summary

When JavaScript code exceeds simple browser automation object orientation can provide a powerful paradigm for controlling complexity. Both of the patterns presented in this article work – the choice is a matter of style. Only one question still remains; who is John Galt?

This article is part of the GWB Archives. Original Author: Liam McLennan

Related Posts