Fundamentals of Web Application Development · DRAFTFreeman

this & Execution Contexts

This is a place in our discussion that I again ask you to be mindful of any underlying assumptions or intuitions that you may bring along from your experience with other programming languages. Because its behavior differs significantly from that of its counterparts in other languages, JavaScript’s this keyword is one of the most widely-misunderstood parts of the language. Due to its seemingly “mysterious” behavior, many developers approach its use in a trial-and-error fashion – or, more commonly, find ways to avoid its use altogether. (It is not difficult to work around, in many cases.)

The next two sections, which cover the this keyword and prototypes, are usually the most challenging aspects of the language – especially when we have preconceived notions to unravel. Our discussion may be fairly dense in spots. You may wish to skim over these sections to get an overall view before starting back here again to dive into the details. These mechanisms are powerful tools, and with our understanding of them we can build useful and efficient design patterns – so let us not shy away from it!

Let us first point out what this is not (but is often thought to be):

  • The current function object itself
  • The current function’s lexical scope

It is true that in JavaScript, functions are objects, so it is not illogical to initially assume that this serves as a reference to the currently-scoped function, or somehow provides a handle to the current lexical scope; however, this is not the case.

The value of this is dependent upon how the function was invoked, not where the function appears in code. Thus, this – on the exact same line of code in the exact same function – can take on different bindings at different points in your program’s execution, depending on how said function is invoked.

The binding of this is entirely determined at run-time based on the current execution context. It is unrelated to the concept of lexical scope.

For example, consider the following code wherein we try to track how many times the function foo is called:1

function foo(num) {
  console.log(num); // Keep track of how many times foo is called

  this.count++;
}

foo.count = 0;

for (let i = 0; i < 5; i++) {
  foo(i); // 0 // 1 // 2 // 3 // 4
}

console.log(`foo was called ${foo.count} times.`); // "foo was called 0 times" <-- !??

Apparently, this.count does not reference the same thing as foo.count, as one might have assumed. So, then… what is this?

Execution Contexts

In order to understand how this is bound, we must first understand the concept of execution contexts. When your program begins to run, the JavaScript engine creates and enters the global (or “base”) execution context. Whenever a function is invoked at run-time, a new execution context is created, pushed onto the top of the execution stack, and immediately entered. The engine then runs through this execution context (possibly entering more new subsequent contexts) until it reaches the end, at which point the context is popped off of the stack and execution of the previously underlying context (which is now once again the top of the stack) continues.

We must look at a function’s call-site in order to determine what this will be bound to at run-time. That is, the binding of this inside of a given function, during a given execution context, is determined by how the current context was created by the context immediately below it on the execution stack.

Default Binding

By default, a function invoked by a standalone function invocation binds this to the global object.

We can imagine how a misinformed use of this may accidentally end up altering global state – not good. In strict mode, this instead defaults to undefined when outside of the global context.

'use strict';

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

let a = 42;

foo(); // throws TypeError: Cannot read property 'a' of undefined

Implicit Binding

An implicit binding is given when a function is invoked through a context object; that is, the function is invoked through a reference on a containing object using a property accessor with dot or array notation. If a function is invoked as such, this is implicitly bound to the context object itself.

function foo() {
  console.log(`Hi, I'm ${this.name}.`);
}

foo(); // "Hi, I'm undefined."

let alice = {
  name: 'Alice',
  greet: foo,
};

alice.greet(); // "Hi, I'm Alice."

let bar = alice.greet;

bar(); // "Hi, I'm undefined."

As we can see, when the function foo is accessed and invoked through the containing object alice, the object alice is itself bound to this – but only during that specific execution context of the function. Notice that this implicit binding appears to be “lost” (rather, it is never actually created) if we then later call the same function using a different reference, such as on line 16 above. Because of how we initialized bar on line 14, it might appear to somehow reference the function foo “through” the property alice.greet; however, it is actually just another direct reference to foo in memory, and the standalone invocation on line 16 will fall back to the default binding rule for this.

Another common way to accidentally “lose” an implicitly-bound this is through subsequent function calls, which create new execution contexts on top of the stack.

function foo() {
  // --> new execution context for foo
  bar();
  function bar() {
    // --> new execution context for bar
    console.log(`Hi, I'm ${this.name}.`);
  }
}

let alice = { name: 'Alice', greet: foo };

alice.greet(); // "Hi, I'm undefined."

Remember: the binding of this depends only on the immediately preceding execution context. Always look at the function’s call-site.

There is, however, a way to make this code work even with nested function executions. At the very beginning of the outer function foo, we can lexically “capture” the contextual value of this by assigning it to a new variable. Sometimes, programmers will call the variable self or that.

function foo() {
  // --> new execution context for foo
  let that = this;
  bar();
  function bar() {
    // --> new execution context for bar
    console.log(`Hi, I'm ${that.name}.`);
  }
}

let alice = {
  name: 'Alice',
  greet: foo,
};

alice.greet(); // "Hi, I'm Alice."

Now, all of the scope contained within foo has access to the variable that according to the normal rules of lexical scope.

Explicit Binding

In JavaScript, functions have a set of utility methods which we can use to explicitly (and arbitrarily) set the binding of this inside of a function for a given execution: call() and apply(). Both of these methods work in a similar fashion.

Let’s take a look at call’s method signature:

func.call(thisArg[, arg1[, arg2[, ...]]])

The first argument, thisArg, is the value of this provided for the invocation of function func. After thisArg, call() optionally takes any number of parameters to pass to func as normal. If func returns a value, that value is in turn returned by func.call().

function foo(salutation) {
  return `${salutation}, I'm ${this.name}.`;
}

foo(); // "undefined, I'm undefined."
let alice = {
  name: 'Alice',
};

foo.call(alice, 'Hi'); // "Hi, I'm Alice."
foo.call(alice, 'Hello'); // "Hello, I'm Alice."

For a function operating in non-strict mode, if thisArg is null or undefined, this will be bound to the global object; if thisArg is another primitive (string, number, or boolean), it will be “boxed” in a native object wrapper of its respective type. In strict mode, the value for thisArg is simply passed to the function as-is.

Here is the method signature for apply():

func.apply(thisArg, [argsArray])

apply() likewise takes thisArg as the first argument. The optional second argument accepts a single array, the elements of which will be passed (“applied”) to func as normal function parameters.

function foo() {
  console.log(`${this.name}'s favorite foods are:`);
  for (let arg of arguments) {
    console.log(arg);
  }
}

let alice = {
  name: 'Alice',
  foods: ['apple', 'banana', 'carrot'],
};

foo.apply(alice, alice.foods);
// "Alice's favorite foods are:"
// "apple"
// "banana"
// "carrot"

alice.speak = foo;
alice.speak(...alice.foods);
// "Alice's favorite foods are:"
// "apple"
// "banana"
// "carrot"

let bob = {
  name: 'Bob',
  foods: ['pizza'],
};

alice.speak.apply(bob, bob.foods);
// "Bob's favorite foods are:"
// "pizza"

As you can see, explicit bindings – using call() or apply() – take precedence over implicit bindings to contextual objects.

We can also create a hard binding using a function’s bind() method. Using bind() does not invoke the function; rather, it creates and returns a new bound function that essentially “wraps” the original function. Here is its method signature:

func.bind(thisArg[, arg1[, arg2[, ...]]])

The returned bound function can then later be invoked in any context but still have its inner this set to the value of thisArg. If any additional arguments are given to bind(), they will be prepended, in sequence, as the first parameters on the bound function whenever it is invoked.

function foo() {
  console.log(`${this.name}'s favorite foods are:`);
  for (let arg of arguments) {
    console.log(arg);
  }
}

let alice = {
  name: 'Alice',
};

let bar = foo.bind(alice, 'banana', 'carrot');

bar('apple');
// "Alice's favorite foods are:"
// "banana"
// "carrot"
// "apple"

let bob = {
  name: 'Bob',
  foods: ['pizza'],
  speak: bar,
};

bob.speak();
// "Alice's favorite foods are:"
// "banana"
// "carrot"

bob.speak.apply(bob, bob.foods);
// "Alice's favorite foods are:"
// "banana"
// "carrot"
// "pizza"

So, altogether, hard-bound bindings take precedence over explicit bindings, which take precedent over implicit contextual bindings, which take precedent over the default binding.

Arrow Functions

Also important to note: arrow functions do not bind their own value for the this keyword. Effectively, the value of this inside of an arrow function takes on the same binding as it would in its lexically-closest-contained, non-arrow-functional scope.

function sayName() {
  console.log(this); // IIFE
  (() => {
    console.log(this.name);
  })();
}

let alice = {
  name: 'Alice',
  talk: sayName,
};

alice.sayName();
// "{ name: 'Alice', talk: [Function: sayName] }"
// "Alice"

That’s a lot of rules to in mind! In practice, though, you will only need to use the this keyword in special circumstances – namely, when creating functions that can be delegated to by multiple objects and which need access to each individual object’s state. (We will discuss delegation in the next chapter.) For a large amount of the code you write, you will probably not have to think about this.


  1. Adapted from an example in Simpson, this &object prototypes, Ch. 1