Index
Published on

Back to basics: JavaScript functions, constructors, and this, demystified

In order to understand how OOP works in JavaScript, you only need to know three programming fundamentals first:

  1. What an “object” is, in any object-oriented language (Java, C#, Python, PHP, etc.).
  2. What an “argument” is, in the context of a function/method/subroutine.
  3. What it means to “call” or “invoke” a function/method/subroutine.

If you got that, you’re ready to understand how functions, constructors, and the this keyword work in JavaScript! If not, learn some more and come back in a bit.

Objects in JavaScript

All[1] objects in JavaScript, including plain objects created by writing {}, have a “prototype” that describes their inherited properties. This is similar to a class definition in other languages, except that a prototype is simply any other object, rather than a special language construct.

So, running var instance = Object.create(prototype) creates a new object, instance, using the object prototype as its prototype. This means that if prototype has a foo property, instance also has the same foo property, because it’s inherited. This type of inheritance is simple and powerful:

var prototype = { foo: 'foo' };
var instance = Object.create(prototype);

prototype.foo; // 'foo'
instance.foo;  // 'foo', because it is inherited

prototype.foo = 'bar';
prototype.foo; // 'bar'
instance.foo;  // 'bar', because it is inherited

instance.foo = 'blah';
prototype.foo; // 'bar'
instance.foo;  // 'blah', because the property `foo` is now overridden

prototype.foo = 'foo';
prototype.foo; // 'foo'
instance.foo;  // 'blah', because it is still overridden on `instance`

delete instance.foo;
instance.foo;  // 'foo', because it the property is no longer overridden

Contrasting prototypal inheritance with traditional class-based models reveals three important differences:

  1. Any[2] object can be a used as a prototype; we are not limited to only using class structures for inheritance in JavaScript. Using the same base object as a prototype over and over again effectively turns that object into a “class”.
  2. In most class-based languages, you cannot modify the properties or methods of a class at runtime. Not so in JavaScript! You can modify the properties of a prototype object at any time, which will change the inherited (non-overridden) values of all objects with that prototype.
  3. The object referred to by the this keyword inside object methods is not fixed at construction time, as it would be with a class, but instead varies according to how the method is called. This is probably the part where you get confused! The rules will be explained in a moment.

Object.create is the “prototypal” way to create a new object that “inherits” stuff from another object, but constructors are more typically used because they are older, more structured, and more understandable for class-based language users. Before we can talk about constructors, though, we first need to look at functions in JavaScript.

Functions in JavaScript

Functions in JavaScript are a special kind of object that contain executable code.

Still, they are just objects like everything else in JavaScript. They can be assigned to multiple variables & properties at the same time; they can be passed around as arguments to other functions.

This extreme flexibility, in combination with the way objects can be dynamically modified at runtime, means that some unique rules exist about how to resolve the this keyword whenever a function is called.

First, let’s quickly rehash how functions are created. Function objects are created using the special function keyword[3]:

var hello = function (name) {
  var greeting = 'hello';
  if (this.greeting) {
    greeting = this.greeting;
  }
  return greeting + ', ' + name;
};

// We'll use this in a second
var obj = {
  greeting: 'hi',
  sayHello: globalFoo
};

In this example[4], we are creating a function assigned to hello, and also an object with that same function assigned at obj.sayHello. (When functions are set as properties of an object, they are often referred to as “methods”, but this is not critical.)

In JavaScript, the prototype object used by all functions is Function.prototype (because the language specification says so). One of the methods on Function.prototype is call (a.k.a. Function.prototype.call, or Function#call when using common shorthand). By invoking this method on our hello function object, we indirectly cause the hello function to be executed:

hello.call(); // 'hello, undefined'

When executing Function#call, the first argument is assigned to the this keyword inside the function, and the remaining arguments are passed as arguments to the function. So, when we call hello.call(obj, 'world'), here’s what things look like inside the function:

Identifier Value
this obj
name 'world'

Simple! Using Function#call means that we always can explicitly tell JavaScript exactly what this means. But it’s kind of long-winded to do that all the time, so for most calls, this form isn’t used.

Shorter—but less simple, and the source of much confusion—is the magic by which JavaScript implicitly assigns the this keyword when you call functions directly, without using Function#call. It’s easiest to explain by comparing how we’d use Function#call to do the same thing that JavaScript does on its own when you directly call a function:

Direct call Function#callequivalent
obj.sayHello('world') obj.sayHello.call(obj, 'world')
hello('world') hello.call(window, 'world')

In the first case, obj is used as the this value because it is the identifier that immediately preceeds the sayHello call in the source code. In the second case, there is no identifier immediately preceeding the hello call, so JavaScript uses the global (window) object instead.[5] These rules will always be followed for every plain JavaScript function. When you pass a function as a callback to another function, the code that uses the callback decides how to invoke it, so that code controls what this means, not yours.

In other words, the magic in JavaScript boils down to this: Whatever object a function is attached to at the time it is called is the object that is identifed by this within the function. If the function is not attached to any object, this is the global (window) object instead.

Here are some examples. Where the arrows point always determines the value of this:

// vvvv
   obj.sayHello('world'); // 'hi, world'
// ^^^^ `this` is `obj`

   var obj2 = {};
   obj2.obj = obj;

//      vvvv
   obj2.obj.sayHello('word!'); // 'hi, word!'
//      ^^^^ `this` is `obj`

// v
    hello('global!'); // 'hello, global!'
// ^ nothing here, so `this` is the global object

//                  vvvvvvvvvv
   obj2.obj.sayHello.call(obj2, 'override!'); // 'hello, override!'
//                  ^^^^^^^^^^ `this` is `obj2`

Constructors in JavaScript

JavaScript constructors are just some language sugar that allow you to write less code (and allow the JS engine to optimise some things for you) when you want to use the same object as a prototype over and over again.

There are only two differences between a regular function and a constructor in JavaScript:

  1. When a constructor is called, the call must be prefixed with the new keyword in order for constructor semantics to apply. Otherwise, as discussed earlier, this will be the global object and your constructor will not do what’s expected. The fact that there is nothing special about a constructor except the way it is called means that, by convention, functions intended to be used as constructors are always differentiated from non-constructors by their uppercase first letter.
  2. The prototype object used by a constructor is always set on the constructor’s prototype property. You can change the prototype of a constructor at any time, but this will only change which object is used as the prototype for new instances, not existing instances.

Since we’ve already talked about how this works, and how prototypes work, the only thing left is to show how the constructor shorthand does what it does for you:

// Here is our constructor; the only way to tell it is a constructor is by the upper case
// first letter
function Person(name) {
  this.name = name;
}
// All functions get their own empty prototype object, just in case you mean to
// make them into a constructor. Which we do here!
Person.prototype.sayHello = function () {
  return 'My name is ' + this.name;
};

// Using the constructor shorthand is super short!
var instance = new Person('Alice');

// Using the “pure” JavaScript longhand is kind of long. :(
var instance = Object.create(Person.prototype);
Person.call(instance, 'Alice');

Combining this with our knowledge from before, we can see that the new keyword calls the constructor function using Function#call, passing in our new instance object to be modified. Not that mystical. Pretty boring, actually. Inheritance is pretty uncomplicated, too, with only a couple of conventional things that we need to do to make it work:

function HappyPerson(name) {
  // We just have to apply the original constructor to our new object
  // from our new constructor...
  Person.call(this, name);
}

// ...and create a new prototype, using the Person “class” as our
// “superclass” prototype object
HappyPerson.prototype = Object.create(Person.prototype);

// JavaScript convention for objects created with a constructor function is
// to define the constructor at `this.constructor`; this allows you to find
// the constructor of any object
HappyPerson.prototype.constructor = HappyPerson;

// And, of course, all our happy people are happy
HappyPerson.prototype.isHappy = true;

One more thing…

Actually, there’s actually one more bit of constructor-related magic. Functions can return values… and so can constructors.

If a constructor function returns an object, even if you call the function with the new keyword, the returned object will be used as the new object instance instead of the new object that was generated by the JavaScript engine before calling the constructor:

function HappyPerson(name) {
  this.isCompletelyDiscarded = true;

  var person = new Person(name);
  person.isHappy = true;
  return person;
}

var happyPerson = new HappyPerson('Bob');

…but keep in mind that if you do this, things like instanceof will not work correctly, since you aren’t actually inheriting from HappyPerson.prototype any more. So don’t make it a habit of returning your own objects from constructor functions. You can! But don’t.


Footnotes

[1] It is possible for host objects (objects that come from non-JavaScript code, like DOM objects) to not have prototypes, because the host language doesn’t have prototypes. It is also possible for JavaScript objects to have a null prototype, but you can only do this by calling Object.create(null). When you create an object with new Object() or its shorthand {}, the object’s prototype is Object.prototype. [2] Some host objects cannot be used as prototypes, for basically the same reason that some host objects do not have prototypes. [3] There are actually two types of functions in JavaScript, function declarations and function expressions. There are some subtle differences that are not relevant to anything written in this post. They can also be created dynamically using new Function, but this shouldn’t be done for security and performance reasons that are outside the scope of this post. [4] Yes, expert JavaScript developer reader, you can (and should) use logical-or (||) to specify default values. Since this is a guide for JavaScript beginners, though, conditional statements simply raise fewer unrelated questions. Same thing when you see me not using Function#apply later. Deal with it :). [5] In “strict mode” JavaScript, this is undefined instead of the global object, which makes more sense for a function being called with no associated object.