Defining and Using Classes in JavaScript

gwagner's picture

The concept of a class is fundamental in object-oriented programming. Objects instantiate (or are classified by) a class. A class defines the properties and methods (as a blueprint) for the objects that instantiate it. Having a class concept is essential for being able to implement a data model in the form of model classes within a Model-View-Container (MVC) architecture. However, classes and their inheritance/extension mechanism are over-used in classical OO languages, such as in Java, where all variables and procedures have to be defined in the context of a class and, consequently, classes are not only used for implementing object types (or model classes), but also as containers for many other purposes in these languages. This is not the case in JavaScript where we have the freedom to use classes for implementing object types only, while keeping method libraries in namespace objects.

Any code pattern for defining classes in JavaScript should satisfy five requirements. First of all, (1) it should allow to define a class name, a set of (instance-level) properties, preferably with the option to keep them 'private', a set of (instance-level) methods, and a set of class-level properties and methods. It's desirable that properties can be defined with a range/type, and with other meta-data, such as constraints. There should also be two introspection features: (2) an is-instance-of predicate that can be used for checking if an object is a direct or indirect instance of a class, and (3) an instance-level property for retrieving the direct type of an object. In addition, it is desirable to have a third introspection feature for retrieving the direct supertype of a class. And finally, there should be two inheritance mechanisms: (4) property inheritance and (5) method inheritance. In addition, it is desirable to have support for multiple inheritance and multiple classifications, for allowing objects to play several roles at the same time by instantiating several role classes.

There is no explicit class concept in JavaScript. Different code patterns for defining classes in JavaScript have been proposed and are being used in different frameworks. But they do often not satisfy the five requirements listed above. The two most important approaches for defining classes are:

  1. In the form of a constructor function that achieves method inheritance via the prototype chain and allows to create new instances of the class with the help of the new operator. This is the classical approach recommended by Mozilla in their JavaScript Guide.

  2. In the form of a factory object that uses the predefined Object.create method for creating new instances of the class. In this approach, the prototype chain method inheritance mechanism is replaced by a copy&append mechanism. Eric Elliott has argued that factory-based classes are a viable alternative to constructor-based classes in JavaScript (in fact, he even condemns the use of classical inheritance and constructor-based classes, throwing out the baby with the bath water).

When building an app, we can use both kinds of classes, depending on the requirements of the app. Since we often need to define class hierarchies, and not just single classes, we have to make sure, however, that we don't mix these two alternative approaches within the same class hierarchy.While the factory-based approach, as exemplified by mODELcLASSjs, has many advantages, which are summarized in Table 1, the constructor-based approach enjoys the advantage of higher performance object creation.

Table 1. Required and desirable features of JS code patterns for classes

Class feature Constructor-based approach Factory-based approach mODELcLASSjs
Define properties and methods yes yes yes
Declare properties with a range (and other meta-data) no possibly yes
Built-in is-instance-of predicate yes yes yes
Built-in direct type property yes yes yes
Built-in direct supertype property of classes no possibly yes
Property inheritance yes yes yes
Method inheritance yes yes yes
Multiple inheritance no possibly yes
Multiple classifications no possibly yes
Allow object pools no yes yes

1.9.1. Constructor-based classes

A constructor-based class can be defined in two or three steps. First define the constructor function that implicitly defines the properties of the class by assigning them the values of the constructor parameters when a new object is created:

function Person( first, last) {
  this.firstName = first; 
  this.lastName = last; 

Next, define the instance-level methods of the class as method slots of the constructor's prototype property:

Person.prototype.getInitials = function () {
  return this.firstName.charAt(0) + this.lastName.charAt(0); 

Finally, class-level ("static") methods can be defined as method slots of the constructor function itself, as in

Person.checkName = function (n) {

An instance of such a constructor-based class is created by applying the new operator to the constructor function and providing suitable arguments for the constructor parameters:

var pers1 = new Person("Tom","Smith");

The method getInitials is invoked on the object pers1 of type Person by using the 'dot notation':

alert("The initials of the person are: " + pers1.getInitials());

When a typed object o is created with o = new C(...), where C references a named function with name "C", the type (or class) name of o can be retrieved with the introspective expression which returns "C" (however the Function::name property used in this expression is not supported by Internet Explorer up to the current version 11).

For defining a subclass in a constructor-based class hierarchy, we use a 3-part code pattern, as recommended by Mozilla in their JavaScript Guide. A class Student is defined as a subclass of Person in the following way. The first step is the definition of the superclass Person above. The second step is the definition of the subclass Student like so:

function Student( first, last, studNo) {
  // invoke superclass constructor this, first, last);
  // define and assign additional properties
  this.studNo = studNo;  

By invoking the supertype constructor with this, ...) for any new object created (referenced by this) as an instance of the subtype Student, we achieve that the property slots created in the supertype constructor (firstName and lastName) are also created for the subtype instance, along the entire chain of supertypes within a given class hierarchy. In this way we set up a property inheritance mechanism that makes sure that the own properties defined for an object on creation include the own properties defined by the supertype constructors.

In the third step, we set up a mechanism for method inheritance via the constructor's prototype property. We assign a new object created from the supertype's prototype object to the prototype property of the subtype constructor and adjust the prototype's constructor property:

// inherit from Person
Student.prototype = Object.create( Person.prototype);
// adjust the subtype's constructor property
Student.prototype.constructor = Student;

By assigning an empty supertype instance to the prototype property of the subtype constructor, we achieve that the methods defined in, and inherited by, the supertype are also available for objects instantiating the subtype. This mechanism of chaining the prototypes takes care of method inheritance. Notice that setting Student.prototype to Object.create( Person.prototype), which creates a new object with its prototype set to Person.prototype and without any own properties, is preferable over setting it to new Person(), which was the way to achieve the same in the time before ECMAScript 5.

Finally, we define the additional methods of the subclass as method slots of its prototype object:

Student.prototype.setStudNo = function (studNo) {
  this.studNo = studNo; 

As shown below in Figure 1, every constructor function has a reference to a prototype object as the value of its prototype property. When an object is created with the help of new, its (unofficial) built-in reference property __proto__ (with a double underscore prefix and suffix) is set to the value of the constructor's prototype property. For instance, after creating a new object with f = new Foo(), it holds that Object.getPrototypeOf( f), which is the same as f.__proto__, is equal to Foo.prototype. Consequently, changes to the slots of Foo.prototype affect all objects that were created with new Foo(). While every object has a __proto__ reference property (except Object), only objects constructed with new have a constructor reference property.

Figure 1. The built-in JavaScript classes Object and Function.

The built-in JavaScript classes Object and Function.

This post has been extracted from the book Building Front-End Web Apps with Plain JavaScript.