What is This in JavaScript?

As we’ve seen in a previous article, scope is an important concept in JavaScript that can sometimes be confusing to developers. On a similar note, context and the this keyword are as important and – unfortunately – just as confusing.

In this article, we’ll cover:

  • What context and this mean in JavaScript;
  • What explicit bindings and hard bindings are;
  • What the new operator represents;
  • How this is different in ES6.

Context and the this Keyword

In JavaScript, functions have a reference to their execution context, which means how they were called. The important nuance to note here is that a function’s execution context is not about how it was declared or what the function does, but about how it is called in the code. This execution context is called this. When you access this from within a function, you’re essentially accessing its execution context.

Contrast that with the lexical scope, which cares about how the function was declared and in what order. That's because the decision for what the scope is gets done during compilation time – as opposed to the context and this – which happens dynamically when the function is called.

Different Ways of Calling a Function

As mentioned earlier, the context depends on how a function was called. That sounds easy – how many different ways can a function be called, right?

In fact, there are 4 different ways a function can be called that will affect the context:

  1. A basic function call.
  2. Calling the function with a context object, also known as implicit binding.
  3. Calling the function using call() or apply(), also known as explicit binding.
  4. By hard binding the this value using bind().

Let’s see how they each affect the context.

Basic Function Call

A basic function call is the simplest way of calling a function and is most likely the first thing a JavaScript developer learns in a hello world app. It looks as follow:

var name = 'John';

function foo () {
    console.log(this.name);
}

foo(); // 'John'

In this example, we simply called the function with foo(), from the global scope. Therefore, the function’s execution context (i.e. how it was called) is the global scope and this will point to the global scope. That is why this.name outputs John.

That said, had we used strict mode by inserting 'use strict' at the top, this would have been undefined because accessing this when a function was called from the global scope is prohibited in strict mode. It is bad practice to access the global scope this way and the compiler assumes that it was not the developer’s intention to do so, and returns undefined to prevent side effects and nasty bugs.

Implicit Binding

The next possible way of calling a function is through implicit binding, which means with a context object. For instance, consider the following code:

const name = 'John';

function foo () {
    console.log(this.name);
}

const myObject = { foo: foo, name: 'Oscar' };

myObject.foo(); // 'Oscar'

In this example, the function foo was called through myObject.foo(), which effectively sets the execution context to myObject. In other terms, how was the function called? It was called from myObject and the output will be Oscar as this will point to myObject and its name, instead of the global scope’s name.

Explicit Binding

It is possible to call a function using the call() or apply() methods, which are very similar except for the parameters they take in. When a function is called through these, we are explicitly binding its execution context to an object. For instance:

const name = 'John';

function foo () {
    console.log(this.name);
}

const myObject = { foo: foo, name: 'Oscar' };

foo.call(myObject); // 'Oscar'

What call() (or apply()) does is explicitly tell the function what object to use as this.

Hard Binding

Feeding off of explicit bindings, there is a stronger way to hard bind which object is used as context, called hard binding. In the sample code above, the function foo can be called by any object using foo.call() and specifying the calling object. Sometimes, we need to prevent this from happening and ensure that foo is always called with the same context.

In other words, we can write something like this:

const name = 'John';

function foo () {
    console.log(this.name);
}

const myObject = { foo: foo, name: 'Oscar' };

const myNewObject = { foo: foo, name: 'Jane' };

const originalFoo = foo;

foo = function () {
    originalFoo.call(myObject);
};

foo.call(myNewObject); // 'Oscar'

What we did here is prevent foo from being called with any context by hard binding it to myObject. This means that regardless of how foo is called, this will always be equal to myObject, hence why the output of the code above is Oscar.

That said, there is now a much easier way of achieving hard binding in JavaScript, thanks to the built-in function bind() introduced in ES5. To quote the MDN documentation, "the bind() method creates a new function that, when called, has its this keyword set to the provided value."

Using bind(), the sample above can be rewritten as follow:

const name = 'John';

function foo () {
    console.log(this.name);
}

const myObject = { foo: foo, name: 'Oscar' };

const myNewObject = { foo: foo, name: 'Jane' };

foo = foo.bind(myObject);

foo.call(myNewObject); // 'Oscar'

This concept is very useful when you want to ensure that your function is always called with the same context, regardless of the execution site is.

For instance, if your function can be called from a button’s click handler, or from a state change event, or directly from another object, bind() allows you to control what the context will always be. This is also a popular pattern in React (but not limited to React), where you hard bind the functions in your constructor so that the context will always be known, regardless of where and how your component is.

The new Operator

At first glance, the new operator may look familiar to Object-Oriented Programming’s new operator, but it does not have the same meaning of creating new instances of an object and is a source of confusion to many.

In JavaScript, calling the new operator on a constructor, for instance new Foo(), does the following:

  • Create a new object, which then inherits from Foo.prototype;
  • This new object created gets passed in as the this context;
  • If the constructor does not return its own object, then the new object is returned.

Here’s what this looks like in code:

function Person (firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

const friend = new Person('John', 'Doe');
console.log(friend.firstName); // 'John'

In the sample above, the newly created object from new is passed in as this, for which we set the first and last name, and then this new object is returned since the constructor does not return an object.

However, if the constructor returned an object, then the newly created object would not be returned, as demonstrated here:

function Person (firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    return { firstName: 'Oscar', lastName: 'Doe' };
}

const friend = new Person('John', 'Doe');
console.log(friend.firstName); // 'Oscar'

What Takes Precedence?

We just covered many ways of calling a function and they all affect what the context is differently. In a situation where more than one of them is applied, what actually takes precedence? How can we identify what the context is if both explicit binding and hard binding are used?

The order of precedence that determines what context is goes as follow:

  1. The new operator
  2. Calling a function using bind()
  3. Calling a function using call() or apply()
  4. Calling a function on a context object (i.e. object.foo())
  5. Global object, except in strict mode

Arrow Functions in ES6

At the start of this article, I mentioned that functions have a reference to their execution context, which means how they were called. This, however, does not apply to arrow functions in ES6.

Arrow functions do not have a this keyword in them. When this is used in an arrow function, the function treats this like any other variable in the lexical scope and looks up this in its parent’s scope. If it does not find it in its parent’s scope, it’ll go up the parent chain until it either finds a variable this or reaches the global scope (keep in mind that strict mode prevents it from accessing the global scope).

Since this in an arrow function behaves like a lexical scope variable, it’s important to understand that the context will change depending on how the function was declared instead of how it was called, which is different from everything we’ve seen so far in this article.

Conclusion

The most important concept to take out of all this is that the context depends on how a function is called, which means that the value of this in the same function can change from one call to another. To determine what the value of this is, it’s best to walk through the order of precedence explained above to identify what affects the context.

Also, do not forget that arrow functions in ES6 are an exception to this rule and that, instead, the lexical scope applies to them and how they are declared is the key factor.

If you're interested in better learning what JavaScript is and how it works at its core, you can follow along on my blog.

Credits

  • The MDN web docs are a great resource for anything JavaScript related.
  • One of my all-time favorite JavaScript teachers is Kyle Simpson, and I highly recommend his You Don’t Know JS book series and video courses on Front-end Masters. I learned a lot from him.

Additional Resources

Header image courtesy of Jason Taellious

Comments