Understanding JavaScript Constructors

Avatar of Faraz Kelhini
Faraz Kelhini on (Updated on )

The following is a guest post by Faraz Kelhini. Some of this stuff is out of my comfort zone, so I asked Kyle Simpson to tech check it for me. Kyle’s answer (which we did during an Office Hours session) was very interesting. It was: 1) This article is technically sound. JavaScript doesn’t really have classes in a traditional sense and this is the way most people shoehorn them in. 2) We may want to stop shoehorning them in. JavaScript has objects and we can use them in the way they are intended to do the same kinds of things. Kyle calls it OLOO (Objects Linked to Other Objects). Here’s an intro. I’d think there is value in learning about both.

Having a good understanding of constructors is crucial to truly understand the JavaScript language. Technically, JavaScript doesn’t have classes, but it has constructors and prototypes to bring similar functionality to JavaScript. In fact, the class declaration introduced in ES2015 simply works as syntactic sugar over the existing prototype-based inheritance and does not really add any extra functionality to the language.

In this tutorial, we will explore constructors in detail and see how JavaScript utilizes them to make objects.

Creating and using constructors

Constructors are like regular functions, but we use them with the new keyword. There are two types of constructors: built-in constructors such as Array and Object, which are available automatically in the execution environment at runtime; and custom constructors, which define properties and methods for your own type of object.

A constructor is useful when you want to create multiple similar objects with the same properties and methods. It’s a convention to capitalize the name of constructors to distinguish them from regular functions. Consider the following code:

function Book() { 
  // unfinished code
} 

var myBook = new Book();

The last line of the code creates an instance of Book and assigns it to a variable. Although the Book constructor doesn’t do anything, myBook is still an instance of it. As you can see, there is no difference between this function and regular functions except that it’s called with the new keyword and the function name is capitalized.

Determining the type of an instance

To find out whether an object is an instance of another one, we use the instanceof operator:

myBook instanceof Book    // true
myBook instanceof String  // false

Note that if the right side of the instanceof operator isn’t a function, it will throw an error:

myBook instanceof {};
// TypeError: invalid 'instanceof' operand ({})

Another way to find the type of an instance is to use the constructor property. Consider the following code fragment:

myBook.constructor === Book;   // true

The constructor property of myBook points to Book, so the strict equality operator returns true. Every object in JavaScript inherits a constructor property from its prototype, which points to the constructor function that has created the object:

var s = new String("text");
s.constructor === String;      // true

"text".constructor === String; // true

var o = new Object();
o.constructor === Object;      // true

var o = {};
o.constructor === Object;      // true

var a = new Array();
a.constructor === Array;       // true

[].constructor === Array;      // true

Note, however, that using the constructor property to check the type of an instance is generally considered bad practice because it can be overwritten.

Custom constructor functions

A constructor is like a cookie-cutter for making multiple objects with the same properties and methods. Consider the following example:

function Book(name, year) {
  this.name = name;
  this.year = '(' + year + ')';
}

The Book constructor expects two parameters: name and year. When the constructor is called with the new keyword, it assigns the received parameters to the name and year property of the current instance, as shown below:

var firstBook = new Book("Pro AngularJS", 2014);
var secondBook = new Book("Secrets Of The JavaScript Ninja", 2013); 
var thirdBook = new Book("JavaScript Patterns", 2010);
 
console.log(firstBook.name, firstBook.year);           
console.log(secondBook.name, secondBook.year);           
console.log(thirdBook.name, thirdBook.year);  

This code logs the following to the console:

As you can see, we can quickly build a large number of different book objects by invoking the Book constructor with different arguments. This is exactly the same pattern that JavaScript uses in its built-in constructors like Array() and Date().

The Object.defineProperty() method

The Object.defineProperty() method can be used inside a constructor to help perform all necessary property setup. Consider the following constructor:

function Book(name) { 
  Object.defineProperty(this, "name", { 
      get: function() { 
        return "Book: " + name;       
      },        
      set: function(newName) {            
        name = newName;        
      },               
      configurable: false     
   }); 
}

var myBook = new Book("Single Page Web Applications");
console.log(myBook.name);    // Book: Single Page Web Applications

// we cannot delete the name property because "configurable" is set to false
delete myBook.name;    
console.log(myBook.name);    // Book: Single Page Web Applications

// but we can change the value of the name property
myBook.name = "Testable JavaScript";
console.log(myBook.name);    // Book: Testable JavaScript

This code uses Object.defineProperty() to define accessor properties. Accessor properties don’t include any properties or methods, but they define a getter to call when the property is read, and a setter to call when the property is written to.

A getter is expected to return a value, while a setter receives the value being assigned to the property as an argument. The constructor above returns an instance whose name property can be set or changed, but cannot be deleted. When we get the value of name, the getter prepends the string Book: to the name and returns it.

Object literal notations are preferred to constructors

The JavaScript language has nine built-in constructors: Object(), Array(), String(), Number(), Boolean(), Date(), Function(), Error() and RegExp(). When creating values, we are free to use either object literals or constructors. However, object literals are not only easier to read but also faster to run, because they can be optimize at parse time. Thus, for simple objects it’s best to stick with literals:

// a number object
// numbers have a toFixed() method
var obj = new Object(5);
obj.toFixed(2);     // 5.00

// we can achieve the same result using literals
var num = 5;
num.toFixed(2);     // 5.00

// a string object
// strings have a slice() method 
var obj = new String("text");
obj.slice(0,2);     // "te"

// same as above
var string = "text";
string.slice(0,2);  // "te"

As you can see, there’s hardly any difference between object literals and constructors. What’s more intersting is that it’s still possible to call methods on literals. When a method is called on a literal, JavaScript automatically converts the literal into a temporary object so that the method can perform the operation. Once the temporary object is no longer needed, JavaScript discards it.

Using the new keyword is essential

It’s important to remember to use the new keyword before all constructors. If you accidentally forget new, you will be modifying the global object instead of the newly created object. Consider the following example:

function Book(name, year) {
  console.log(this);
  this.name = name;
  this.year = year;
}

var myBook = Book("js book", 2014);  
console.log(myBook instanceof Book);  
console.log(window.name, window.year);

var myBook = new Book("js book", 2014);  
console.log(myBook instanceof Book);  
console.log(myBook.name, myBook.year);

Here’s what this code logs to the console:

When we call the Book constructor without new, we are in fact calling a function without a return statement. As a result, this inside the constructor points to Window (instead of myBook), and two global variables are created. However, when we call the function with new, the context is switched from global (Window) to the instance. So, this correctly points to myBook.

Note that in strict mode this code would throw an error because strict mode is designed to protect the programmer from accidentally calling a constructor without the new keyword.

Scope-safe constructors

As we have seen, a constructor is simply a function, so it can be called without the new keyword. But, for inexperienced programmers, this can be a source of bugs. A scope-safe constructor is designed to return the same result regardless of whether it’s called with or without new, so it doesn’t suffer from those issues.

Most built-in constructors, such as Object, Regex and Array, are scope-safe. They use a special pattern to determine how the constructor is called. If new isn’t used, they return a proper instance of the object by calling the constructor again with new. Consider the following code:

function Fn(argument) { 

  // if "this" is not an instance of the constructor
  // it means it was called without new  
  if (!(this instanceof Fn)) { 

    // call the constructor again with new
    return new Fn(argument);
  } 
}

So, a scope-safe version of our constructor would look like this:

function Book(name, year) { 
  if (!(this instanceof Book)) { 
    return new Book(name, year);
  }
  this.name = name;
  this.year = year;
}

var person1 = new Book("js book", 2014);
var person2 = Book("js book", 2014);

console.log(person1 instanceof Book);    // true
console.log(person2 instanceof Book);    // true

Conclusion

It’s important to understand that the class declaration introduced in ES2015 simply works as syntactic sugar over the existing prototype-based inheritance and does not add anything new to JavaScript. Constructors and prototypes are JavaScript’s primary way of defining similar and related objects.

In this article, we have taken a good look at how JavaScript constructors work. We learned that constructors are like regular functions, but they are used with the new keyword. We saw how constructors enable us to quickly make multiple similar objects with the same properties and methods, and why the instanceof operator is the safest way to determine the type of an instance. Finally, we looked at scope-safe constructors, which can be called with or without new.