Object Prototypes and this

Learning goals

  • Software design approaches and patterns, to identify reusable solutions to commonly occurring problems
  • Apply an appropriate software development approach according to the relevant paradigm (for example object-oriented, event-driven or procedural)

You have probably heard of “object-oriented” programming languages. Javascript allows you to apply an object-oriented paradigm in your development, but it behaves differently from pure object-oriented languages. This topic explains the principles underlying Javascript’s object model.

By the end of this topic, you should understand object prototypes, [[Prototype]] links, and [[Prototype]] chains; and exactly what this means in any particular context.

Object prototypes

Objects in JavaScript have an internal property usually written as [[Prototype]]. These [[Prototype]] properties are not accessible directly; instead, they are references to another object. Almost all objects are given a non-null value for this property when they’re created. So what’s [[Prototype]] used for?

var foo = {
  fizz: 42,
};

var bar = Object.create(foo);

console.log(bar.fizz); // 42

What Object.create does will become clear shortly. For now, you can treat it as creating an empty object that has a [[Prototype]] link to the object passed to it — in this case, foo.

So what’s a [[Prototype]] link?

You’ll notice that bar.fizz exists, which isn’t something that normally happens with empty objects, and in fact is 42. What’s happening is that when fizz isn’t found on bar, the linked object is checked for fizz, at which point foo.fizz is found.

In general, when an object property is accessed, the object itself is checked for the property, and then the linked object, and the linked object’s linked object, and so forth, up the [[Prototype]] chain, until there isn’t a linked object. If it’s still not found, only then is undefined returned as the property’s value.

So where is the “end” of the [[Prototype]] chain? At the top of every (normal) chain is the object Object.prototype, which has various utility functions, like valueOf and toString — this explains why objects can be coerced since they are all by default linked to Object.prototype, which has these methods.

Now look at the following example:

var foo = {
  fizz: 42,
};

var bar = Object.create(foo);

console.log(bar.fizz); // 42

foo.fizz = 11;

console.log(bar.fizz); // 11

Hopefully, this result shouldn’t surprise you! When we access bar.fizz for the second time, bar hasn’t changed at all, so it as usual goes up the [[Prototype]] chain, comes to foo, and discovers foo.fizz exists, and has value 11. What this illustrates is the concept of changing object [[Prototype]]s after creating the link.

This gives you the power to do clever things — for example, it’s a common way to polyfill features that are missing from an engine on which you need the JavaScript to run, in other words, to implement those missing features. For example, Arrays have a method map that exists in ES5 onwards; if you need to support an engine that doesn’t support ES5 and want to use map, you’ll need to polyfill the functionality. The next step is to realise that all Arrays in JavaScript are automatically [[Prototype]] linked to Array.prototype, which means if you add the map method to Array.prototype, all your Arrays will gain the map feature!

if (!Array.prototype.map) {
  Array.prototype.map = function (callback, thisArg) {
    /* ... Implementation here ... */
  };
}

However, this power also allows you to change data in unexpected ways after it’s been declared and assigned. In our example with foo and bar, if you were reading the code and needed to debug a problem, it’s not immediately obvious that assigning foo.fizz would also change the value we get back when accessing bar.fizz. This makes it harder both to understand written code and to find and fix bugs. Use this power responsibly and with the care it deserves.

Shadowed Properties

Consider the following example:

var foo = {
  fizz: 42,
};

var bar = Object.create(foo);

console.log(bar.fizz); // 42

bar.fizz = 11;

console.log(bar.fizz); // 11
console.log(foo.fizz); // 42

If bar already has a property fizz when we try bar.fizz = 11, then the existing property is changed, just like you’re used to.

If bar doesn’t have the property fizz, then the [[Prototype]] chain is traversed. If none of the linked objects have fizz, then it’s created on bar and assigned the value 11, also just like you’re used to.

What happens when bar doesn’t have fizz but an object in the [[Prototype]] chain does? In this case, a new property is added to bar, resulting in a shadowed property. Sound familiar? A shadowed variable is created if a variable is re-declared, and in that scope, the shadowed variable is accessed rather than the variable in the parent scope. For our shadowed properties, the analogy holds. When checking the [[Prototype]] chain, the shadowed property is found first and accessed first. (There are a couple of exceptions where shadowed properties aren’t created, to do with read-only properties and setters, but they’re not important for us right now.)

As a final note, know that shadowing can occur implicitly as well. It can be very subtle and hard to spot.

var foo = {
  fizz: 42,
};

var bar = Object.create(foo);

bar.fizz++;

console.log(foo.fizz); // 42
console.log(bar.fizz); // 43

At first glance, you could easily think that what bar.fizz++ does is look up fizz, find it on foo, and then increment that. However, the ++ operation is actually shorthand for bar.fizz = bar.fizz + 1, which accesses the current value of fizz, 42, adds 1 to it, and then assigns it to bar.fizz, and since foo.fizz already exists, a new shadowed property fizz is created on bar. Remember, if you wanted to modify foo.fizz, you should have modified foo.fizz and not something else.

this

Info

this is a common word in English – if it appears like this, we’re talking about the keyword. In other cases, like “this” or this or this, we’re just speaking plain English.

If you’ve ever done any programming in an object-oriented language, you might think that this refers to the current instance of a class, which might translate to JavaScript as referring to the method’s parent object. Alternatively, you could intuit that this refers to the currently executing function or the current scope. Sadly, this is a complicated beast that often causes confusion if you don’t have a clear understanding — the answer is d) None of the above! It is context-sensitive.

The first thing to realise about this is that what it does is dynamic rather than lexical, or alternatively that it’s a runtime binding rather than an author-time binding. It is contextual based on the conditions of the function’s invocation: this has nothing to do with the function where it’s used, but instead has everything to do with the manner in which the function is called. This is where we look at the call-site: the location from which a function is called — we have to look at this exact spot to find what this is a reference to.

fizz();

function fizz() {
  // Call-site is "fizz()" in the global scope
  foo.bar();
}

var foo = {
  bar: function bar() {
    // Call-site is "foo.bar()" in fizz()
    buzz();
  },
};

function buzz() {
  // Call-site is "buzz()" in bar()
}

So we know where the call-site is, and what’s at the call-site. How does this help us with what this is? There are four rules which govern this.

1. Default Binding

This first rule is the most common case for function calls when they’re standalone function invocations. It’s the “default” in that you can think of it as the rule which applies when none of the other rules apply.

function think() {
  console.log(this.expectedAnswer);
}

var expectedAnswer = "42";

think(); // 42, but only in browsers

The call-site is the line think();, which leads to this referring to the global object. The global object depends on the platform on which the JavaScript is run. For instance, on browsers, it’s the window object, and additionally, variables declared with var in the global scope are also on the global object; whereas in Node, for example, it’s the global object, and variables in the global scope aren’t on the global object. Additionally, there are differences in behaviour between when this is used while in the global context and when it’s the call-site that’s in the global context. You’ll need to research what the global object is for whatever engine you’re developing for.

Additionally, check whether you’re in strict mode (usually signified by "use strict"; appearing in your code somewhere). If you’re in strict mode, default binding is just undefined.

The takeaway from this is that what you get back from this when default bound depends on your engine and the state of strict mode, which can make it more trouble than it’s worth. You’ll find you rarely have much need for using this in this way: however, this rule is a common source of bugs with this – it’s important to know about this rule so you can tell when it’s happening.

2. Implicit Binding

This next rule is for when the call-site has a context object. Intuitively, this is when you’re calling an object’s method.

function think() {
  console.log(this.expectedAnswer);
}

var deepThought = {
  expectedAnswer: 42,
  think: think,
};

deepThought.think(); // 42

The call-site deepThought.think() uses deepThought as the context object, and this rule says that it’s that context object which is used for this. You’ll also notice that the function think() is declared separately and then added by reference to deepThought. Whether or not the function is initially declared on deepThought or added as a reference, the call-site uses the deepThought context to reference the function.

Loss of Implicit Binding

This binding might seem familiar to those who have experience in other languages, as this looks like it refers to the object the method “owned by”. However, remember that functions aren’t really “owned” by objects, they’re just referred to, just like in the example. Sticking to that (incorrect) way of thinking can lead to one of the most common frustrations with this, which is when an implicitly bound function loses the binding, which usually means it ends up going back to the default binding.

function think() {
  console.log(this.expectedAnswer);
}

var deepThought = {
  expectedAnswer: 42,
  think: think,
};

var logAnswer = deepThought.think;

logAnswer(); // undefined

Although logAnswer looks like it’s a reference to deepThought.think, it’s actually a reference to the function think, which deepThought.think was referring to.

One of the most common examples of this happening is when callbacks are involved. Things like:

setTimeout(deepThought.think, 3000); // undefined (in three seconds' time)

If you think about how setTimeout (and other callbacks) work, you can imagine them a bit like this:

function setTimeout(callback, delay) {
  /* Do some waiting */
  callback();
}

The call-site doesn’t have any context objects, it’s just using the default binding.

If you wanted to have this refer to deepThought, this could make it very awkward if you ever need to pass think around as a value. Thankfully, there are ways to force this to refer to specific objects.

3. Explicit Binding

Functions in JavaScript have on their [[Prototype]] some utilities that help with our predicament.

call and apply

Both of these methods take as their first parameter an object to use for this, and then invoke the function with it.

function think() {
  console.log(this.expectedAnswer);
}

var deepThought = {
  expectedAnswer: 42,
};

think.call(deepThought); // 42

They do have slight differences, but that’s the important bit for us. Check out their documentation: call and apply.

However, this doesn’t help us with the problem of passing methods while keeping this referring to the original object.

bind

This method, again, takes this as the first parameter, but what it does is return a new function with this referring to the object.

function think() {
  console.log(this.expectedAnswer);
}

var deepThought = {
  expectedAnswer: 42,
  think: think,
};

setTimeout(deepThought.think.bind(deepThought), 3000); // 42 (in three seconds' time)

Like call and apply, bind does a little more than that; you can find the docs here) You’ll also notice that the call-site deepThought.think.bind(deepThought) looks like it’s using both implicit binding and explicit binding. We’ll get to how the rules interact with each other shortly.

this or that

Another common situation is when you have (possibly nested) callback functions:

var sherlock = {
  name: "Sherlock Holmes",
  inspect: function () {
    setTimeout(function () {
      console.log("Deducing...");
      setTimeout(function () {
        console.log(this.name); // What is `this`?
      }, 1000);
    }, 1000);
  },
};

sherlock.inspect(); // Deducing... undefined

You should be able to deduce yourself why this is bound incorrectly inside setTimeout.

Because there are two functions introduced, fixing the problem requires two bind calls:

var sherlock = {
  name: "Sherlock Holmes",
  inspect: function () {
    setTimeout(
      function () {
        console.log("Deducing...");
        setTimeout(
          function () {
            console.log(this.name);
          }.bind(this),
          1000
        );
      }.bind(this),
      1000
    );
  },
};

sherlock.inspect(); // Deducing... Sherlock Holmes

Using bind repeatedly can start to look a little ugly, so a common alternative pattern is to explicitly assign this to a new variable (often that or self).

var sherlock = {
  name: "Sherlock Holmes",
  inspect: function () {
    var that = this;
    setTimeout(function () {
      console.log("Deducing...");
      setTimeout(function () {
        console.log(that.name);
      }, 1000);
    }, 1000);
  },
};

sherlock.inspect(); // Deducing... Sherlock Holmes

(This is rarely required with ES6 arrow functions, as you’ll see in a later topic).

4. new Binding

Again, a common source of misconceptions for those with experience in other languages. The syntax for the use of new is basically identical to other languages but with some differences. In other languages, you might use new to instantiate a new instance of a class by calling its constructor; but JavaScript doesn’t have classes (or at least not in the same sense. More details in the ES6 section coming up).

Let’s first make sense of constructors in JavaScript — there is no such thing as a constructor function in JavaScript. You can call any function and use a new in front of it, which makes that function call a constructor call. It’s a subtle distinction to be sure, but it’s important to remember.

So what does new do? When you invoke a function with new in front of it, this is what happens:

  1. A new (empty) object is created.
  2. The new object is [[Prototype]] linked to the function’s prototype property.
  3. The function is called with this referring to the new object.
  4. Unless the function returns its own alternate object, it will then return the newly constructed object.

Let’s put this all together and do something interesting with it:

function Planet(name, answer) {
  this.name = name;
  this.answer = answer;
}

Planet.prototype.logAnswer = function () {
  console.log(this.answer);
};

var earth = new Planet("Earth", "42");
var magrathea = new Planet(
  "Magrathea",
  "This is a recorded announcement as I'm afraid we're all out at the moment."
);

earth.logAnswer(); // 42
magrathea.logAnswer(); // This is a recorded announcement as I'm afraid we're all out at the moment.

Putting the rules together

After the above, it’s relatively straightforward how to deduce what this is when more than one rule applies. Look at the call-site and apply them in order:

  • Is the function called with a new binding? If so, this is the newly constructed object.
  • Is the function called with an explicit binding? This can be either a call or apply at the call-site, or a hidden bind at declaration. If so, this is the specified object.
  • Is the function called with an implicit binding? If so, this is the context object.
  • If none of the above, the function was called with the default binding. If in strict mode, this is undefined, otherwise it’s the global object.

(Remember, this in the global scope — not in a function at all — can work differently than when it is in a function. Check your engine’s documentation!)

ES6 continued

“Classes”

Go back over that last example with Planet. If you’ve used classes either in JavaScript or in other languages, you’ll notice that var earth = new Planet(...) looks very much like an object instantiation, and the bit before defining Planet looks like a constructor and a method. Now look at the following code, written using ES6 classes:

class Planet {
  constructor(name, answer) {
    this.name = name;
    this.answer = answer;
  }
  logAnswer() {
    console.log(this.answer);
  }
}

var earth = new Planet("Earth", "42");
var magrathea = new Planet(
  "Magrathea",
  "This is a recorded announcement as I'm afraid we're all out at the moment."
);

earth.logAnswer(); // 42
magrathea.logAnswer(); // This is a recorded announcement as I'm afraid we're all out at the moment.

The keyword class is what is called syntactic sugar for creating a function intended for use with new, and then adding methods to the function’s prototype property — in other words, the implementation is still the same, while its appearance is neater and easier to read.

The syntactic sugar can cause subtle problems down the line, though, due to it not actually implementing classes, but being a cover over [[Prototype]] linking.

class Planet {
  constructor(name, answer) {
    this.name = name;
    this.answer = answer;
  }
  logAnswer() {
    console.log(this.answer);
  }
}

const earth = new Planet("Earth", "42");
earth.logAnswer(); // 42

Planet.prototype.logAnswer = function () {
  console.log("EXTERMINATE");
};

earth.logAnswer(); // EXTERMINATE

The use of class has the implication that the class’s properties and methods are copied onto a completely separate object. However, in JavaScript, you can change or replace methods in these “classes” after declaration, and all instances of this “class” that were previously instantiated will still be affected. Now you know about how it actually works under the hood, you know this behaviour makes sense (you’re updating the object to which the instances are linked), but the fact of the matter is that it’s surprising that a class can be changed later and affect all its instances.

You should write your code with deliberation and care, taking into account the benefits and drawbacks of your options. class has its place — it makes your code easier to read, and makes the mental model with the hierarchy of objects in your code easier to comprehend — just make sure you know how to use it well.

Object oriented design patterns

Design patterns are common ways of solving problems in software development. Each pattern is a structure that can be followed in your own code, that you implement for your specific situation. Following established design patterns is advantageous because:

  • in general they have evolved as best practice, and so avoid the need for you to solve every problem anew – although “best practice” changes over time so some established patterns fall out of favour; and
  • other developers will probably be familiar with the patterns so it will be easier for them to understand and maintain your code.

Although design patterns aren’t specifically tied to object oriented programming, most of the common design patterns that have arisen fit the OOP paradigm. The following are some common OOP design patterns.

  • Singleton: This pattern ensures that there is only ever one instance of a class. This could be important if your application needs a single shared configuration object or an object manages access to an external resource. This would be implemented by:

    • Having a static property in the class that can hold the singleton
    • Making the class constructor private so that new objects of that class cannot be created
    • Having a static public method that is used to access the singleton; if the shared object doesn’t exist yet then it is created
  • Factory: This is a pattern for creating objects that share an interface, without the caller needing to know about the various classes that implement that interface. For example, a factory pattern for creating objects that have the interface Product could look like:

    • Two classes that implement Product are Pen and Book
    • The interface ProductFactory has the method createProduct(), which creates and returns Product objects
    • The class PenFactory implements ProductFactory, and its createProduct() method creates and returns a new Pen
    • The class BookFactory implements ProductFactory, and its createProduct() method creates and returns a new Book
    • If code elsewhere in the application is given a ProductFactory for creating products, it doesn’t need to know what the products are or be told when new products are created
  • Model-View-Controller: You’ll be familiar with the MVC pattern from the Bootcamp Bookish exercise; it’s a very common way to structure user interfaces.

    • Model objects contains the data that is to be displayed (e.g., an entity that has been fetched from a database)
    • The View is a representation of the Model to the user (such as a table or graphical representation)
    • The Controller processes commands from the user and tells the Model objects to change appropriately
  • Adapter: This pattern would be suitable for the Bootcamp SupportBank exercise, in which you had to process data from files in different formats (CSV, JSON and XML). An adapter is a class that allows incompatible interfaces to interact. In the case of SupportBank, you might decide that:

    • TransactionReader is an interface that has the method loadTransactions(), which returns an array of Transaction objects
    • There are three implementations of TransactionReader, each of which knows how to parse its particular file type, convert values where necessary and produce Transaction objects
    • Note that you might use a Factory pattern to create the TransactionReaders, so it’s easy to add support for new file types in future

Strengths of OOP

The following are strengths of the object oriented programmming paradigm.

Abstraction refers to the fact that object oriented code hides information that you don’t need to know, so if you’re using an object all you know about it is its publicly visible characteristics. Your code doesn’t know about how the object’s data and methods are implemented. This is especially clear when dealing with interfaces – code that interacts with an interface knows only the contract that the interface publishes, and that interface might be implemented by lots of different classes that have completely different structures and behaviour.

Encapsulation refers to the fact that the data and behaviour of an object are tied together into a single entity. If you think about programming without encapsulation, if you have a simple data structure with a set of fields in it and then want to run a function on it, you need to make sure that you find the function that correctly handles that specific data structure. With encapsulation, you just call the object’s own method.

Polymorphism refers to the fact that an object can be interacted with as if it were an instance of an ancestor class, while its behaviour will come from its actual class. When a new descendent class is defined with its own implementation of ancestor class methods, code that interacts with the base class will automatically call the new implementations without having to be updated.