Clearing Up Common Misconceptions about the this Keyword in JavaScript

Clearing Up Common Misconceptions about the this Keyword in JavaScript

·

7 min read

The JavaScript this keyword often finds itself at the center of debates among developers, labeled as a source of confusion that contributes to JavaScript's perceived complexity. However, delving into its intricacies reveals that this isn't as complex as it's made out to be, provided one grasps its nuances.

Though, to truly comprehend what this is, we must first understand what it isn’t.

This article draws inspiration from Kyle Simpson's book,**this & Object Prototypes*, specifically reflecting on insights gained from* Chapter 1: "this or That?".*Here, I aim to distill my understanding of this fundamental JavaScript concept, shedding light on its true nature amidst common misconceptions.*

Why this?

Why do we need this?

this lets us reuse functions against multiple context objects. This means we can use the same function on different objects without the need to create separate functions for each object.

I’ll use the example Kyle provided.

var me = {
  name: 'Kyle',
};

var you = {
  name: 'Reader',
};

function identify(context) {
  return context.name.toUpperCase();
}

function speak(context) {
  var greeting = "Hello, I'm " + identify(context);
  console.log(greeting);
}

identify(you); // READER
speak(me); // Hello, I'm KYLE

Here we’re simply passing the objects, me and you into the speak and identify functions. This implementation works fine, for now. However, as your codebase scales, you might encounter challenges. For instance, if you have numerous functions that need access to the object's properties, you'll end up duplicating the passing of the object into each function. This not only leads to code duplication but also makes maintenance cumbersome.

The alternative, using this, is a cleaner, smoother way to achieve the same result.

var me = {
  name: 'Kyle',
};

var you = {
  name: 'Reader',
};

function identify() {
  return this.name.toUpperCase();
}

function speak() {
  var greeting = "Hello, I'm " + identify.call(this);
  console.log(greeting);
}

identify.call(me); // KYLE
identify.call(you); // READER

speak.call(me); // Hello, I'm KYLE
speak.call(you); // Hello, I'm READER

By using this, we can pass along the object reference without passing the object itself, thereby avoiding code duplication and simplifying maintenance as the codebase grows.

What this isn’t

We haven’t completely tackled what this is, but let’s talk about what it isn’t.

this is not Itself

this does not reference itself. It is a common misconception that this refers to the function itself. Although we may want to reference a function from within itself, through recursion or having an event handler that can unbind itself when it’s first called, this isn’t the way to achieve that.

Trying to add self-referencing properties inside the function fails because of this misconception.

Let’s look at another one of Simpson’s snippet.

function foo(num) {
  console.log('foo: ' + num);

  // keep track of how many times `foo` is called
  this.count++;
}

foo.count = 0;

var i;

for (i = 0; i < 10; i++) {
  if (i > 5) {
    foo(i);
  }
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log(foo.count); // 0 -- WTF?

This function is a poor attempt at trying to record how many times the function was called. We add a count property to foo()we can do that because all functions are objects, just callable objects — to keep track of how many times foo() was called, but count remains 0 even though it’s clear it was called four times.

Why didn’t this work?

this isn’t pointing to foo(). Like Kyle said ‘The frustration stems from a tooliteralinterpretation of what this (inthis.count++) means.’

For the this.count reference inside of the function, this is not in fact pointing at all to that function object, and so even though the property names are the same, the root objects are different, and confusion ensues.

Kyle Simpson - this & Object Prototypes

So if we aren’t incrementing the correct count property, which count did we increment?

After the function is called, this inside foo() points to the global window object, and this.count is adding a count property to said object with a value of undefined initially.

So even though we added foo.count, nothing acts on it. Though if you look at the window object you’ll see it has a value of NaN because subsequent calls to foo() tries to increment undefined.

To avoid this issue, you might be tempted to store the variable in a top-level variable.

function foo(num) {
  console.log('foo: ' + num);

  // keep track of how many times `foo` is called
  data.count++;
}

var data = { count: 0 };

var i;

for (i = 0; i < 10; i++) {
  if (i > 5) {
    foo(i);
  }
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log(data.count); // 4

We’ve solved the problem thanks to lexical scope, but did we really? We’ve ignored the real problem which is that we don’t know how this behaves.

To solve this, we should consider forcing this to point to the actual foo() function object.

function foo(num) {
  console.log('foo: ' + num);

  // keep track of how many times `foo` is called
  // Note: `this` IS actually `foo` now, based on
  // how `foo` is called (see below) 
  this.count++;
}

foo.count = 0;

var i;

for (i = 0; i < 10; i++) {
  if (i > 5) {
    // using `call(..)`, we ensure the `this`
    // points at the function object (`foo`) itself
    foo.call(foo, i);
  }
}

// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log(foo.count); // 4

By using call we are able to specify the value to use as this when calling foo(). Here we are setting this to point to foo() .

this is not its Scope

this does not refer to a function’s lexical scope.

function foo() {
  var a = 2;
  this.bar();
}

function bar() {
  console.log(this.a); //undefined
}

foo();

There are a couple mistakes in this snippet.

  1. We are trying to reference bar(), by calling this.bar(). This only works because function declarations in the global scope, are added to the global window object my default. This would’ve failed had bar() been a function expression.

  2. bar() is attempting to gain access to foo()’s lexical scope by using this. In other words, its trying to gain access to a by using this. What happens instead, is that inside bar(), this points to the window object where the a property does not exist. Accessing a non-existent property on an object produces undefined.

The developer who writes such code is attempting to use this to create a bridge between the lexical scopes of foo() and bar(), so that bar() has access to the variable a in the inner scope of foo(). No such bridge is possible. You cannot use a this reference to look some‐thing up in a lexical scope. It is not possible.

Every time you feel yourself trying to mix lexical scope look-ups with this, remind yourself: there is no bridge.

Kyle Simpson - this & Object Prototypes

What’s this?

We know what this isn’t — itself nor it’s scope— so what is this?

this is a special identifier keyword that’s automatically defined in the scope of every function.

this is not an author-time binding but a runtime binding. It is contextual based on the conditions of the function’s invocation. this binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.

Kyle Simpson - this & Object Prototypes

To put it simply, we won’t know the value of this until the function is called. this is not something we can tell the value of by just looking at the code. The value of this is not determined during the function's definition but rather during its execution.

When a function is called, its execution context is created. The execution context has information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference, which will be used for the duration of that function’s execution.

Conclusion

To understand what this is, we must first understand what it isn’t. this is neither a reference to the function itself, nor is it a reference to the function’s lexical scope.

this is actually a binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called.

thanks for reading