Front-end education for the real world. Since 2018.





JavaScript, when is this?

Mat “Wilto” Marquis

Topic: JavaScript

I spent the first few years of my career regarding this as yet another aspect of my primarily hunch-driven approach to web development—which is to say, sometimes it felt like this might be what I wanted, but if so, it was for reasons I couldn’t possibly begin to understand.

I was occasionally right—to my very slim credit—but not because I knew why or anything. Now, after years of muddling through on vibes, trial and error, and squinting at Stack Overflow for as long as impending deadlines would allow, I think I’ve gotten a pretty good handle on this. I mean, give or take an emotional support console.log every so often; I’m only human.

For me, the trouble was always that this is contextual, but that context isn’t meaningful to us developers so much as it’s meaningful to JavaScript. You and I are used to telling JavaScript what things are: this is the identifier we typed, where we typed it, scoped to the pair of brackets we typed around it, and it has the value we assigned to it.

That’s not the case with this, which is—brace for it—a keyword that refers to the object bound to the function where this is invoked at the time when that function is called.

That is an absolute nightmare of a sentence, I know, but bear with me. It breaks down to two important concepts: this references the object bound to a function, and the object referenced by this is determined at the time a function is called. Once you’ve got a handle on those two aspects, this is— well, still a little weird, but the knowable kind of weird.

At a high level, “the object bound to a function” makes sense when you frame it in terms of the essential nature of JavaScript: objects with methods referencing objects with methods, all the way down. Almost any code we execute anywhere in a script relates to an object in one way or another, either explicitly or implicitly. I wouldn’t say that’s an uncomplicated topic, strictly speaking, but we’ll get to that in a bit.

We can’t understand the what without first understanding the when, and for my money, “at the time a function is called” is what makes this particularly tricky to get a handle on. What determines the value of this isn’t the writing, but the calling of a function—meaning that the value of this inside a function could be different every time that function is invoked. In order to really make sense of this, we have to think more like JavaScript executing a function than a person at a keyboard writing a function.

Execution Contextspermalink

To better understand how JavaScript “thinks,” we need to better understand JavaScript’s execution model and the nature of the call stack—the “first in, last out” data structure that a JavaScript interpreter uses to execute code.

Again, you and I are used to thinking about the JavaScript we write in terms of the JavaScript we’re writing. We’re calling the shots: when, where, and how we declare a variable and assign it a value communicates its relevancy to JavaScript.

Code language
js

function theFunction() {
  const theVariable = true;
};

When a JavaScript engine encounters this code, it creates a lexical environment for theFunction—a data structure that represents the code as written.

All the context needed to execute that function—our variable, in this example—is added to the environment record for the function. If you’ve ever wondered how JavaScript can be aware of variables prior to declaring them (but gives you a hard time about it), those environmental records are why:

Code language
js

function theFunction() {
  console.log( theVariable );

  let theVariable;
}

theFunction();
// result: Uncaught ReferenceError: can't access lexical declaration 'theVariable' before initialization

The environmental record created for this function’s execution context contains a theVariable property, because it’s right there in the lexical environment for the function. JavaScript is aware of our variable at the time it executes the function and encounters that console.log, but we’re doing something pretty weird with it, so JavaScript throws an error to save us from ourselves. That environmental-record-based “awareness” is how variable hoisting works, but that is a topic for another time.

After making lexical sense of the function and creating the environmental record, the JavaScript engine creates an execution context (sometimes called a “frame”) for our function. That execution context includes the function and everything required to execute it.

The Call Stackpermalink

You can think of the call stack as a sort of playlist made up of these function execution contexts: it determines what code is executed and when. This probably goes without saying, but there’s a lot going on in and around the call stack—if you’re interested in learning the gritty details about how it works and how it impacts the code we write, well, do I ever have a course for you.

For now, and purposes of understanding this, here’s the short and synchronous version of how the call stack works.

Given the following script:

Code language
js

const theValue = 4;

function checkTheValue( theFunctionVariable ) {
  const successMsg = "High enough.";
  const failureMsg = "Too low.";

  if( theFunctionVariable > 2 ) {
    consoleLogger( successMsg );
  } else {
    consoleLogger( failureMsg );
  }
};

function consoleLogger( msg ) {
  console.clear();
  console.log( msg );
};

checkTheValue( theValue );

When a script is executed, the JavaScript interpreter creates a “global execution context” and pushes it to the call stack. Any statements inside that global context are then executed one at a time, from top to bottom.

Diagram. A 'bucket' representing the stack, alongside an arrow pointing from the top toward the bottom. The stack contains a block labelled global execution context. Inside the global execution context block, each block from the above example is higlighted

As soon as the interpreter encounters that checkTheValue function call within the global execution context, it creates a function execution context for that call at the top of the stack. The global execution context is put on hold, and the newly-added function execution context is immediately executed.

Referencing the previous illustration, the 'checkTheValue( theValue )' statement in the global context is highlighted, and an arrow points to a 'checkTheValue function execution context' block that has been added above the global execution context block. It contains the following statements: const successMsg = ‘High enough’, const failureMsg = ‘Too low’ and if ( theFunctionVariable > 2  consoleLogger( successMsg )  else  consoleLogger( failureMsg )

That function execution context contains a call to consoleLogger, so a function execution context for that call is added to the top of the stack and—-you guessed it—executed immediately.

The 'consoleLogger( sucessMsg )' line in the checkTheValue function execution context is highlighted, and an arrow points from there to a newly-added “consoleLogger function execution at the top of the stack.

Once consoleLogger has concluded, it gets removed from the stack, and JavaScript continues executing the checkTheValue function that called it.

The "consoleLogger function execution context"”" block has been removed from the top of the stack, leaving the “checkTheValue function execution context” block and “global execution context” block.

Our checkTheValue function concludes, is popped off the stack, and the global execution context continues on.

The 'checkTheValue function execution context' block has been removed from the top of the stack, leaving only the global execution context

Well, it would continue on if it had anything else to do. Since there aren’t any other statements to execute, that gets removed as well—“first in, last out.” At that point there’s nothing left in the call stack, so our script has concluded.

When is the value of this determined?permalink

A number of things happen when a JavaScript interpreter creates an execution context, one of which is setting a value for the keyword this within the scope of that execution context. Except in one specific case, this doesn’t depend on the lexical environment—we don’t dictate the value for this within the scope of the execution context for the function where we reference it. The value referenced by this within the execution context depends on how that function was called: whether a function is called as a standalone function, a method, or a constructor can change that value.

For my money, this is the heart of the confusion around this. When we write a function that contains const theVariable = true, it doesn’t matter what route a JavaScript engine takes to reach that statement: the lexical environment contains a statement initializing theVariable, so the immutable fact that theVariable exists is marked down in the environmental record. If and when that statement is reached in the execution context, the value of theVariable is true, full stop:

Code language
js

const theObject = {
  theMethod() {
    const theVariable = true;
    console.log( theVariable );
  }
};

theObject.theMethod();
// result: true

You couldn’t possibly be faulted for assuming this works the same way, despite the fact that JavaScript is doing the setting instead of us (don’t worry about the specifics in this example just yet):

Code language
js

const theObject = {
  theMethod() {
    console.log( this );
  }
};

theObject.theMethod();
// result: Object { theMethod: theMethod() }

We’ll talk about the nature of “the object bound to the function” in the second part of this article series for sure, but here we see that this logged inside theMethod() results in the object containing that method (theObject). Even for all my disclaimers, this snippet of code probably still reads a lot like this in the method refers to the object because, well, look at it—the method is right there in that object. However, if we invoke the same method in a slightly different way:

Code language
js

const theObject = {
  theMethod() {
    console.log( this );
  }
};

const theFunctionIdentifier = theObject.theMethod;

theObject.theMethod();
// result: Object { theMethod: theMethod() }

theFunctionIdentifier();
// result: Window {...}

There’s that contextual weirdness. The same method and the same object—but if we invoke the method a little differently, the value of this changes:

Code language
js

const theObject = {
  theMethod() {
    console.log( this === theObject );
  }
};

const theFunctionIdentifier = theObject.theMethod;

theObject.theMethod();
// result: true

theFunctionIdentifier();
// result: false

At the time the function is called.”

Not the most intuitive thing in the world, granted, but at least the when for setting the value of this makes a kind of deep-down-in-the-JavaScript-machinery sense.

Without understanding the “when” of this, we wouldn’t be able to make sense of the “what”—specifically, what objects this could reference, depending on how a function is called. In the second part of this series of articles that’s exactly what we’re going to do, and there’s good news: there are only a handful of different cases that determine the object referenced by this.

Catch you in the next one.


With thanks to Jake Archibald for checking all of the specifics were in order.

Enjoyed this article? You can support us by leaving a tip via Open Collective


Newsletter