Understanding the fundamental concepts of a programming language can go a long way. In JavaScript, the this
keyword is one such cornerstone.
The this
keyword in JavaScript can be a tough nut to crack. Some beginners struggle with the wording, others with its dynamic nature.
In this tutorial, we’ll attempt to demystify JavaScript’s this
keyword and, in doing so, help you practice debugging issues that would otherwise be quite complex.
this
keyword?With so many things going on within just a few lines of code behind the scenes, there are two fundamental questions you must answer to execute said code:
The this
keyword helps to answer where the code iss because it’s part of what’s known as the execution context. The execution context defines the neighborhood of a piece of code. Unfortunately, it’s poorly named, which leads to some confusion among new JavaScript developers.
By default, all code is executed in the global execution context, as opposed to a local execution context like a function body. Think of the global execution context as your courtyard and the local execution context as your house.
The scope is what defines the visibility/accessibility of the code within an execution context. For example, to say that the variable cats
is out of scope for function catMaker()
means that the function catMaker()
does not have access to the variable cats
because cats
is not in the scope chain of catMaker()
. The scope chain defines all the variables a particular execution context can access.
It’s a lot of information to take in, but you really need to understand this to understand this
. If you’re still struggling to follow along, don’t sweat it — you’re not alone. Try reading it again or head to the resources section to find more helpful guides.
this
?Let’s look at some places where you’re likely to encounter the this
keyword.
The global execution context is the default execution context, and within it is a local execution context. If you were to write some code, the respective contexts would be defined as follows.
let myName = "John Doe"; // global execution context function sayName() { // local execution context console.log(myName); }
In the global execution context, the value of this
is the same as what is known as the window
object in the browser. Think of the window object as representing a tab (because it contains all kinds of fancy details about it) in a browser. To verify that this
is the same as the window
object in the global execution context, you can just run the following piece of code.
console.log(this === window); // prints true
Functions have their own execution context and scope, but if you define a function in the global execution context, the value of this
will be, again, the same as the window object.
function someFunc() { return this; } someFunc() === window; // returns true
This may or may not be desirable. If you’d like to avoid this, you can enable what is known as the strict mode in JavaScript. This literally forces JavaScript to throw more errors where appropriate, ultimately yielding to code that is more predictable. When the strict mode is enabled, this
will yield to undefined.
function someFunc() { "use strict" console.log(this); } someFunc(); // returns undefined
There may also be cases where you want to change what this
is for a function to something else, more or less to change the context of that function. To do this, you can use the functions call()
, apply()
, and bind()
. Starting with the latter, the bind()
function binds a function with the value of this
that you provide and returns a new function.
const obj = { message: "hello world" } function printMessage() { console.log(this.message); }; const boundPrintMessage = printMessage.bind(obj); printMessage(); // prints undefined boundPrintMessage(); // prints "hello world"
The bind()
function is a very powerful tool that can help you create reusable code and solve some tricky situations, some of which we’ll look at later on.
If you want to avoid returning a new function bound to a value of this
, you should consider using call()
or apply()
. call()
and apply()
both allow you to call a function with a value of this
that you provide, except with call()
, you can pass in parameters to the function, and with apply()
, you pass those parameters as an array.
const user = { name: 'John Doe' } function printUser(likes) { console.log(`My name is ${this.name}, and I like ${likes}`) }; printUser.call(user, 'apples') printUser.apply(user, ['apples'])
Arrow functions, also known as ES6 fat arrow functions, are almost identical to plain functions, with a few critical exceptions. For one, unlike with plain functions, the value of this
does not default to the window
object. You can demonstrate this by declaring an arrow function in an object.
const obj = { message: "hello world", arrowFunc: () => console.log(this.message), plainFunc: function() { console.log(this.message); } } obj.arrowFunc() // prints undefined obj.plainFunc() // prints hello world
Because arrow functions don’t have their own value of this
in this situation, it’s not recommended to use arrow functions as object methods. You might think that, since bind()
allows you to change the value of this
for a function, you can use bind()
to avoid this behavior, but that won’t work.
const obj = { message: "hello world", arrowFunc: () => console.log(this.message), plainFunc: function() { console.log(this.message); } } const boundArrowFunc = obj.arrowFunc.bind(obj); boundArrowFunc(); // prints undefined
call()
, apply()
, and bind()
were introduced to allow functions to execute in a scope that you define, but the value of this
in arrow functions depends on the scope it was defined in.
ES6 classes always operate in strict mode, so the value of this
for classes is not the same as the window
object. As you may know, though, ES6 classes are a kind of syntax sugar, so if you were to write an ES6 class in traditional function style, the value of this
will be the window object.
The value of this
in classes depends on how they are called. As such, there might be instances where you want to set the value of this
to be that of the class’ instance.
class Person { constructor() { this.name = "John Doe" this.sayName = this.sayName.bind(this); // Try running the code without this line } sayName() { console.log(this.name); } } const somePerson = new Person(); somePerson.sayName(); const sayName = somePerson.sayName; sayName();
If you’re familiar with using React, you might be familiar with this pattern — called hard-binding — when writing class components. When you do not bind the value of this
in your event handlers to that of the class, the value of this
tends to be undefined.
this
could resolve toWe’ve gone over some of the most common cases, but, of course, there are other situations you may encounter. Refer to the tips below to help determine what this
could resolve to in a given scenario.
this
is the same as the window
object in the browserthis
isthis
is the same as the window
object when strict mode is not active. When strict mode is enabled, this
is undefined, as it should bethis
resolves to the object in which the function is defined, while an arrow function would refer to the enclosing execution contextthis
should be for a function, you should use call()
, apply()
, or bind()
.Understanding the fundamentals of JavaScript will help you immensely when you start wrangling with complicated frameworks and libraries. It’s imperative to have a solid understanding of topics like the this
keyword if you want to learn how to debug and write error-free code that doesn’t behave weirdly.
Don’t worry if you don’t get it right away — topics this complex can take a while to sink in. To achieve a clear understanding, you must write code, get a feel for the situations we described in this post, and solve the issues that arise by trial and error. That will help solidify your understanding and get you to the next step.
this
keyword — A more technical take on the this
keyword exploring various examples and caveats. This article is worth bookmarking as a reference guide
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDesign React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.
Fix sticky positioning issues in CSS, from missing offsets to overflow conflicts in flex, grid, and container height constraints.
From basic syntax and advanced techniques to practical applications and error handling, here’s how to use node-cron.