The Onist

The Onist


Tags


Tips for JavaScript Constructors

Learn how prototypical inheritance works and why your class constructers in JavaScript need to be as light as possible.

Before we even begin, lets get on the same page and realize that there are no classes in JavaScript. When someone refers to a "class" in JavaScript, they are actually referring to a constructor function. This is because JavaScript is not a class-based language, but rather a prototype-based language. Phew, OK. Now that we have that covered, lets delve into constructor functions and why they should be as lightweight as possible.

Classic Constructor Function

Take a look at the standard, contrived Dog class below.

// constructor
function Dog (name, breed) {
  // instance variables
  this.name = name;
  this.breed = breed;
  
  // instance functions
  this.bark = function() {
    alert("WOOF!");
  };
  
  this.getTrickList = function() {
    return []; // stupid dog
  };
}

Pretty basic example and it seems like a great Dog class from a Java/C# perspective. Yet the truth of the matter is that this is a probably the wrong implementation in most scenarios. The problem here is that everything declared in the constructor on the this object is now bound to that instance of the constructor. This doesn't appear to be a huge issue on the surface. However, the important thing to know and remember is that each time an instance of the Dog class is created via new Dog(), every function on this is re-declared. This becomes quite the detriment to performance when there are a lot of Dog instances being created.

Show Me Your Prototype

The more performant way to create this constructor is to place the functions on the object's (constructor's) prototype instead of this. This approach has three main advantages:

By using a prototype, we will save memory because each of methods defined on the prototype will only be declared once and all objects created using the constructor will have access to those methods. That's pretty neat!

Reworking the example above to use a prototype gives us the following.

function Dog (name, breed) {
  // instance variables
  this.name = name;
  this.breed = breed;
}

Dog.prototype.bark = function() {
  alert("WOOF!");
};

Dog.prototype.getTrickList = function() {
  return [];
};

One Size Does Not Fit All

As with any design pattern in software development, the prototype approach described above may not always be the best choice. For example, the biggest disadvantage of setting methods on the prototype is that access to local private variables are the lost. This may or may not be an issue depending on how your application is architected. However, if access to private variables is needed then perhaps declaring instance functions is the better approach.

Mixing Both Approaches

We have now learned that both the prototype and constructor stuffing approaches have their own advantages and disadvantages. The good news here is that we can mix the approaches to get both better performance and private variables.

Expanding on the Dog class from prior, lets now assume that we want to be able to check if a given Dog has any medical issues. Well, as we all know, medical records are confidential and should be kept private from the others and the outside world.

function Dog (name, breed) {
  // instance variables
  this.name = name;
  this.breed = breed;
  
  var medicalRecords = {
    "hasWorms": true,
    "hasDiabetes": false
  };
  
  this.checkMedicalRecordsForIssues = function() {
      if (medicalRecords.hasWorms || medicalRecords.hasDiabetes) {
         return true;
      }
      return false;
  }
}

Dog.prototype.bark = function() {
  alert("WOOF!");
};

Dog.prototype.getTrickList = function() {
  return [];
};

Dog.prototype.hasMedicalIssues = function() {
  return this.checkMedicalRecordsForIssues();
};

The above shows how we can combine the two approaches to accomplish our needs. Thanks for reading, hopefully you learned something!

Share Post
View Comments