Skip to content

Instantly share code, notes, and snippets.

@danielscottjames
Last active Feb 7, 2020
Embed
What would you like to do?
What is `this`? Why is that?

What is this? Why is that?

Buckle Up

The rise of JavaScript is incredible, and might partly be explainable by its low barrier of entry. Language features like functions and this generally work how you might expect. To be proficient in JavaScript, you don't necessarily need to know the nitty gritty details. But eventually, every JavaScript developer WILL find a bug and realize that the cause was because this isn’t what was expected.

Then you might start to wonder, how exactly does this work in JavaScript? Isn’t this something you expect to see in Object Oriented Programming (OOP)? Is JavaScript even an OOP language? If you Google that, you might find someone mentioning prototypes, what exactly are those anyways? What did the new keyword do before the class keyword existed?

All these concepts are intertangled. In order to explain how this works, I'm going to take some detours. Rather than go over a detailed enumeration of the rules of this, I want to provide motivation for why JavaScript is the way it is.

Object Oriented Programming (OOP) in JavaScript

The prototype programming paradigm in JavaScript can be used to achieve OOP. Even before you could write a class statement in JavaScript, JavaScript was and always has been an OOP language.

JavaScript is a simple language and only uses a few moving parts to accomplish OOP principles. Some of the most important parts which I will talk about in this post are: functions, closures, this, prototypes, records (AKA the object literal), and new.

Encapsulation and Reusability via Closures

Let’s create a Counter class. A counter should have methods to reset or increment the counter. We could write something like:

function Counter(initialValue = 0) {
    let _count = initialValue;

    return {
        reset: function() {
            _count = 0;
        },
        next: function() {
            return ++_count;
        }
    }
}

const myCounter = Counter();
console.log(myCounter.next()); // -> 1

We are only using functions and object literals, no this or new. Yet, we've already achieved some good OOP results here. We have a way to create new Counter instances. Each Counter instance has its own internal count variable. Here we have achieved some encapsulation and reusability in a purely functional way!

A Performance Problem

Let's say we write a program, and that program uses a lot of counters. Every counter would have its own reset and next methods. (Notice that Counter().reset != Counter().reset) Creating these closures for every method for every instance of any class in your program would require a large amount of memory usage! This isn't a case of premature optimization either, this would be so inefficient that it would not really be a viable language design. Ideally, we'd like a way to only store the properties of an instance of Counter, and share the code for the methods between all counters. (This is in essence what all OOP languages, like Java, would do.)

Without any additional language features to work with, we might end up with something like:

let Counter = {
    reset: function(counter) {
        counter._count = 0;
    },
    next: function(counter) {
        return ++counter._count;
    },
    new: function(initialValue = 0) {
        return {
            _count: initialValue
        }
    }
}

const myCounter = Counter.new();
console.log(Counter.next(myCounter)); // -> 1

This approach solves the performance problem, but we made some pretty big compromises. Each counter instance shares an implementation, but the programmer must be painfully involved to make it all work. However, without additional language features, we might be stuck with this approach.

this to the Rescue!

We can rewrite the above example to be a better experience using the this keyword.

let Counter = {
    reset: function() {
        this._count = 0;
    },
    next: function() {
        return ++this._count;
    },
    new: function(initialValue = 0) {
        return {
            _count: initialValue,

            // Adds a reference to each method for each reference,
            // but does not create a new function
            reset: Counter.reset,
            next: Counter.next
        }
    }
}

const myCounter = Counter.new();
myCounter.next();

// Hopefully calling reset on another instance doesn't reset myCounter
(Counter.new()).reset();

console.log(myCounter.next()); // -> 2

Notice that we are still only creating a single reset and next functions. (ex. Counter.new().reset == Counter.new().reset) Notice how in the previous example we had to give the shared implementation of these methods a handle to the instance that we were calling these methods on for the program to work. Now we can just call myCounter.next() and reference the instance with this. But how does this work? reset and next are declared on the Counter object, how does JavaScript know what this refers to when these functions are called?

Calling JavaScript Functions

You may have noticed that functions in JavaScript have a call method. (There's also an apply method which is very similar, the difference here is not important.) Using call lets you specify what the value of this should be when calling the function. Notice how our Counter example using this looks just like the previous example when we use call:

const myCounter = Counter.new();
Counter.next.call(myCounter);

In fact, this is exactly what the dot notation is doing behind the scenes when it invokes the function! lhs.fn() is evaluated as fn.call(lhs).

Now it should be clear, this is a special identifier inside a function that is set when the function is called!

And the Bugs Begin

Let's say that you want to create a counter and increment it every second. This seems like a reasonable approach:

const myCounter = Counter.new();
setInterval(myCounter.next, 1000);

// Some time later
console.log(`Why is ${myCounter.next()} still 0??`)

Do you see the bug here? We are giving the shared next function to the setInterval. When the interval goes off, the function is called with undefined as this and nothing happens. This would be identical to setInterval(Counter.next, 1000); One common solution to this problem might be:

const myCounter = Counter.new();
setInterval(function () {
    myCounter.next();
}, 1000);

What About bind?

We could imagine a more general way to solve this problem. Consider this approach:

function bindThis(fn, _this) {
    return function(...args) {
        return fn.call(_this, ...args)
    };
};
const myCounter = Counter.new();
setInterval(bindThis(myCounter.next, myCounter), 1000);

Using our wrapper function, bindThis, we can ensure that Counter.next is always called with myCounter as this no matter how the new function is called! Notice that this doesn't actually modify the Counter.next function. JavaScript has the bind functionality built in, already. The following line is identical to the example above: setInterval(myCounter.next.bind(myCounter), 1000);

Cleaning up Counter using Prototypes

Right now we have a pretty good Counter class, but it's still a little messy to write. The ugliest part might be the lines:

// ...
reset: Counter.reset,
next: Counter.next,
// ...

What we still need is a better way to share the class implementation with all instances of the class. This is the problem that prototypes solve in JavaScript. If you try to access a property or function on the object and it does not exist, the JavaScript interpreter will try the prototype instead. You can set the prototype of an object by using Object.setPrototypeOf. Let's rewrite our Counter class using prototypes:

let Counter = {
    reset: function() {
        this._count = 0;
    },
    next: function() {
        return ++this._count;
    },
    new: function(initialValue = 0) {
        this._count = initialValue;
    }
}

function newInstanceOf(Klass, ...args) {
    const instance = {};
    Object.setPrototypeOf(instance, Klass);
    instance.new(...args);
    return instance;
};

const myCounter = newInstanceOf(Counter);
console.log(myCounter.next()); // -> 1

The new keyword in JavaScript

Our approach using setPrototypeOf is very similar to how the new operator in JavaScript works. The biggest difference is that new will use the prototype of the constructor function passed in. Therefore, instead of creating an object for our class methods, we just put the methods on the constructor function's prototype. Rewriting our code to use new gets us:

function Counter(initialValue = 0) {
    this._count = initialValue;
}
Counter.prototype.reset = function() {
    this._count = 0;
}
Counter.prototype.next = function() {
    return ++this._count;
}

const myCounter = new Counter();
console.log(`${myCounter.next()}`); // -> 1

We have finally arrived at code that you could actually see in the wild! Before the class statement existed, this was a standard way that classes in JavaScript were written and instantiated.

The class keyword in JavaScript

Hopefully at this point it's clear why we are using the constructor function's prototype and how this works in the function methods. However, this still doesn't seem like a very friendly approach. Fortunately, these days there's an even better way to declare a class in JavaScript using the class keyword:

class Counter {
    reset() {
        this._count = 0;
    }

    next() {
        return ++this._count;
    }

    constructor(initialValue = 0) {
        this._count = initialValue;
    }
}

const myCounter = new Counter();
console.log(`${myCounter.next()}`); // -> 1

The class keyword doesn't do significant magic behind the scenes. You could think of it as just syntax sugar over the previous prototype approach above. In fact, if you run the above example through a transpiler targeting ES3, you might end up with something like:

var Counter = /** @class */ (function () {
    function Counter(initialValue) {
        if (initialValue === void 0) { initialValue = 0; }
        this._count = initialValue;
    }
    Counter.prototype.reset = function () {
        this._count = 0;
    };
    Counter.prototype.next = function () {
        return this._count++;
    };
    return Counter;
}());
var myCounter = new Counter();
console.log(myCounter.next());

Notice how the transpiler generated code that was essentially identical to the previous example!

What about arrow functions?

If you've written JavaScript in the last 5 years, you might be wondering why I haven't mentioned arrow functions yet. My general advice is, "always use an arrow function unless you know you need a regular function." It just so happens to be that defining constructors and class methods is one of those specific situations where you actually need to use regular JavaScript functions. Fortunately (or unfortunately for clarity's sake), the class keyword obfuscates that need for most people.

How does this work in arrow functions?

Some people might say that arrow functions bind the current value of this when they are created. That is technically wrong (no this value is bound, it is looked up via lexical scope), however, it's a good mental model to have. Every time you see an arrow function you can mentally rewrite it as:

const myArrowFunction = () => {
    this.doSomething();
}

// is basically...

const _this = this;
const myRegularFunction = function() {
    _this.doSomething();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment