DEV Community

Cover image for Explaining "this" Keyword In JavaScript Like You're Five
Victor Ayomipo
Victor Ayomipo

Posted on • Originally published at vayo.hashnode.dev

Explaining "this" Keyword In JavaScript Like You're Five

this is one of the most difficult JavaScript concepts to understand, no doubt about that. If you understood it on your first exposure to it, you're probably one of the greatest JavaScript prodigies to ever walk this earth. Although, if you can't seem to wrap your head around it, don't worry, I'd make you a prodigy.

In this article, You are going to see a substantial amount of code snippets and visual illustrations, this is just to give you a better understanding of JavaScript this keyword. The code snippets are not complex, I promise.

You will learn

  • What this means in JavaScript and how it works under the hood

  • this in the global scope

  • this in functions and arrow functions

  • this in object methods

  • this in constructor functions

  • this in classes

  • this in event handlers

  • Best practices when using this


Imagine you are at a smartphone store trying to purchase the latest Samsung device. You found the exact Samsung device and went to the checkout line to finalize the purchase. As you approach the cashier, you noticed a long queue of people waiting to make their own purchases.

After a few minutes, it got to your turn and you show the cashier the Samsung phone you want to purchase, the cashier then begins the checkout process. As the cashier scans the phone and begins to process your payment, you notice that he keeps using the word "this". The cashier writes some stuff on the receipt like "this phone is the latest model" and "this purchase was paid through cash". It made sense to you because he was referring specifically to the phone you were purchasing, not any other Samsung device in the store.

In JavaScript, this works in a similar way. When you call a function using an object, this refers to the object. So, just as the cashier uses "this" to refer to the specific Samsung device you want to buy, this in your JavaScript code refers to the object that invoked the function.

Visual illustration of how

In the illustration above, when the Samsung object calls the function, this in that function references the Samsung object; when the iPhone object calls the function, this references the iPhone object.

this refers to the object that calls the function at the time of the function call. this keyword always refers to either an object, undefined or null. Nothing else.

The code snippet below is a quick example of how this works -

function thisValue() {
  return this;
}

const newPhone = { 
  name: "Samsung S23 Ultra",
  price: "$900",
};

newPhone.thisValue = thisValue;

console.log(newPhone.thisValue());
// Output: newPhone {name: "Samsung S23 Ultra", price: "$900", thisValue: ƒ}
Enter fullscreen mode Exit fullscreen mode

The thisValue function was added to the newPhone object as a method (functions in an object). Therefore, when the newPhone object invokes the thisValue method, it returns newPhone since this refers to the object that calls a function.

Now, let's imagine you want to access the brand of the newPhone object, you could create a method that returns the newPhone brand, like this newPhone.name(works!) or you could say this.name(also works!)

const newPhone = {
  name: "Samsung S23 Ultra",
  price: "$900",
  thisName() {
    return `I own the ${newPhone.name}.`; // using object name
  },
};

// or

const newPhone = {
  name: "Samsung S23 Ultra",
  price: "$900",
  thisName() {
    return `I own the ${this.name}.`; // using this keyword
  },
};
Enter fullscreen mode Exit fullscreen mode

However, there's a slight problem. Since everything in JavaScript (e.g array, functions, etc.) are all objects under the hood, there are times you'd want an object to be created from another object. For example, an object called oldPhone can be created from an existing object newPhone, like this

const newPhone = {
  name: "Samsung S23 Ultra",
  brand: "Samsung",
  touchScreen: true,
  thisName() {
    return `I own the ${newPhone.name}.`;
  },
};

//Object.create() creates an object based on an existing object.
const oldPhone = Object.create(newPhone);
oldPhone.name = "Samsung J5";
console.log(oldPhone.thisName()); // outputs: ???
Enter fullscreen mode Exit fullscreen mode

What do you think oldPhone.thisName() would output? It outputs: "I own the Samsung S23 Ultra." which shouldn't be because we need the oldPhone name and not the newPhone name. Since thisName() method is already explicitly tied to only return newPhone name, trying to call the function with another object (in this case oldPhone object) would still return newPhone.name.

To correct this, thisName() method should reference the name property using this.name rather than newPhone.name so that it works correctly with any object that has a name property.

const newPhone = {
  name: "Samsung S23 Ultra",
  brand: "Samsung",
  touchScreen: true,
  thisName() {
    return `I own the ${this.name}.`; // changed to this.name
  },
};

const oldPhone = Object.create(newPhone);
oldPhone.name = "Samsung J5";
console.log(oldPhone.thisName());
// outputs: I own the Samsung J5. Works as expected
Enter fullscreen mode Exit fullscreen mode

Using this.name instead of newPhone.name is better because it allows the code to be more flexible and reusable. Just know that when you use this in an object method, this would mostly refer to the object that invokes the method (except for arrow functions, more on that later).

Did you know?

The this keyword is like a hidden parameter that is passed to a function when it is called. You can set the value of this to a specific object by using the dot notation (.) to prefix the function with the object. This way, the object that you specified will be used as the this context, for example newPhone.thisValue().

It's a powerful feature of the language, but it can be tricky to use correctly, so make sure you understand it well.

this In Global Scope

Before we begin, let's have a quick understanding of how this works in JavaScript strict mode.

Strict mode is a mode that was introduced to JavaScript for the main purpose of eliminating silent errors and allowing developers to write clean code. You can apply strict mode to your JavaScript by adding 'use strict'; at the top level of your JavaScript i.e before you start coding or doing anything else or at the top level of a function block if you prefer not to apply it to your whole JavaScript code.

Whether in strict mode or not, this keyword used at the top level of a script will always refer to the global object(or window object).

Although, when strict mode is used inside a function, and the function is invoked without an object attached to it, this would return undefined instead of the global object as the normal JavaScript non-strict mode does. This makes it much easier to spot bugs in the long run.

Visual illustration of what

Let's see how this works in terms of JavaScript scoping. In the global scope, this refers to the global object. In a browser environment, the global object is the window object, while in a Node.js environment, it is the global object.

These options...

  • variables and object properties that are given the value this in the global scope,

  • functions that are invoked in the global scope with no object attached to it,

...they all return the global object when assigned to the this keyword.

let foo = this;
let foo1 = { value: this };
function foo2() { 
  return this;
}

console.log(foo === window); // true
console.log(foo1.value === window); // true
console.log(foo2() === window); // true
Enter fullscreen mode Exit fullscreen mode

In the code above, the variable foo has this keyword assigned to it and since the variable was created in the global scope, the this keyword can't refer to any other object so it defaults to the global object.

The variable foo1 is an object with one of its properties assigned to the this keyword. It can be a little tricky since this mostly references objects but you have to know that, it is only functions found in an object that works that way. Object properties (not object methods) that are assigned this refers to the global object because this is not inside a method, it's just in the global scope.

The foo2 function acts differently though. A function that returns the value of this depends on how it's invoked; since the foo2 function was not invoked as a method of any object, the this keyword in the function defaults to undefined (strict mode) or the global object (non-strict mode).

this In Functions & Arrow Functions

Using this in a normal function is actually straightforward, the this keyword in the function would refer to the object that called it or the object it's bound to, a quick example -

//create a function that returns `this`
function playMusic() {
  return `${this.name} is playing music.`;
}

//create objects
const newPhone = {name: "Samsung S23 Ultra"};
const oldPhone = {name: "Samsung J5"};

//copy the function as methods to the object created
newPhone.playMusic = playMusic;
oldPhone.playMusic = playMusic;

console.log(newPhone.playMusic()); //Samsung S23 Ultra is playing music.
console.log(oldPhone.playMusic()); //Samsung J5 is playing music.
Enter fullscreen mode Exit fullscreen mode

When the function was copied as a method to the two objects, you might have thought "why not invoke it?". Well if the function was invoked before storing it inside the object, it is the returned value of the function that'd be stored in the object, not the function itself. In essence, doing that would just store a string to newPhone.playMusic that'd output "[object Window] is playing music." because the function was called with no object attached to it so this would default to the global object.

❕ Useful Tips

In functions, this refers to the dynamic context in which that particular function is called:

  • It can reference the object that invoked it.

  • It can be explicitly bound to a different object than the one that created it by using call(), apply(), or bind().

  • If invoked without the function binding to any object implicitly or explicitly, it references the global object.

Now, going through the steps of creating a function with this keyword and copying it as a method to different objects can be quite stressful. Instead, you can just create the function as an object method in one object and make other objects call the object method anytime. To achieve this, Javascript has 3 in-built functions that can help with that:

  • call

  • apply

  • bind

These three functions can come in handy when you have an object method that uses this keyword to reference and update stuff around that object, but you also need a similar object method like that for other objects. Therefore, rather than creating original object methods for each object, you can simply borrow the object method you need from the object that already owns it. These methods can surely save you a ton of time and effort when working with complicated objects or extensive codebases.

Using Call

The call function executes a function and explicitly set the this keyword to reference a desired object. The syntax is call(thisArg, ...arguments) where "thisArg" is the object you want this keyword to refer to and "...arguments" are other arguments(separated by a comma) that the object method might need. For example

//object with the playMusic method
const newPhone = {
  name: "Samsung S23 Ultra",
  playMusic() {
    return `${this.name} is playing music.`;
  },
};

// objects that need the playMusic method
const momPhone = { name: "Samsung S10" };
const dadPhone = { name: "Samsung A80" };

//using playMusic method for other objects that needs it
console.log(newPhone.playMusic.call(momPhone)); //Samsung S10 is playing music.
console.log(newPhone.playMusic.call(dadPhone)); //Samsung A80 is playing music.
Enter fullscreen mode Exit fullscreen mode

The code snippets above have three objects; two objects momPhone and dadPhone do not have a playMusic method, only one object newPhone has a playMusic method. Now, obviously you'd want all phone objects to be able to play music but creating playMusic method for each object can be tasking when dealing with lots of objects. Instead, we kind of took a copy of the playMusic method from the object that has it and used it for the two other objects that needed it. The call() method makes this keyword inside the playMusic method to reference the desired object that was passed as an argument to call(), instead of the original object that created the method.

Using apply()

The apply function also invokes an object method that uses this keyword and binds it to another object. Although, arguments to be used by apply() are passed as a single array unlike call(). The syntax is apply(thisArg, [...arguments]) where "thisArg" is the object you want this keyword to refer to and "[...arguments]" are other arguments that are passed in an array form that the object method might need. For example,

//object with the playMusic method
const newPhone = {
  name: "Samsung S23 Ultra",
  playMusic(song, artist) {
    return `${this.name} is playing "${song}" by ${artist}.`;
  },
};

// objects that need the playMusic method
const oldPhone = { name: "Samsung J5" };
const dadPhone = { name: "Samsung A80" };

//using playMusic method for other objects that needs it
console.log(newPhone.playMusic.apply(oldPhone, ['Calm down','Rema'])); //Samsung J5 is playing "Calm down" by Rema.
console.log(newPhone.playMusic.apply(dadPhone, ['Bad', 'Michael Jackson'])); //Samsung A80 is playing "Bad" by Michael Jackson.
Enter fullscreen mode Exit fullscreen mode

By using apply() in the code above, we were able to use this keyword from the playMusic method to reference our desired objects rather than the object that owns the playMusic method. We also passed in an array argument to tweak the result a little bit.

Differences between call() and apply()

They are both incredibly similar with just one difference, which is the way arguments are passed to them.

  • call() accepts arguments in list-comma separated form -

    call(thisArg, arg1, arg2, arg3)

  • apply() accepts arguments in a single array-list form -

    apply(thisArg, [arg1, arg2, arg3])

Yes, yes, I know what you're thinking. Why would you want to pass an array as an argument when you could just follow the normal way that call() uses?

Well, there are instances when you'd need to use apply() instead of call(). For instance,

  • The arguments to be used are being passed dynamically by a server so you don't know the fixed number of arguments to pass.

  • The arguments you want to use have already been stored in an array.

  • You might want to use the length of the arguments you passed.

Using apply() when you come across any of these scenarios would make your JavaScript journey easier.

Use bind()

If you noticed, when we were using the call() and apply() functions on the object method, the object method was invoked immediately to reference the desired object as the this value. However, in some cases, it might be necessary to bind the this value of an object method to a new object without invoking it immediately. In such situations, the bind() function can be used.

The bind() function creates a new function whereby the object method that uses the this keyword would be bound to the desired object passed as an argument. The returned function can then be invoked anytime you wish and this would always refer to the desired object that was bounded.

The bind() syntax is bind(thisArg, ...arguments) where the "thisArg" parameter is the desired object that this should refer to & the "...arguments" parameters are the list of arguments(separated by commas) that the function might need.

One of the basic uses of bind() is to create a function that will always be called with a specific value of this, regardless of how it is invoked. this would never refer to any other object apart from the desired object it was bound to.

//object with the accessInternet method
const newPhone = {
  name: "Samsung S23 Ultra",
  accessInternet(hasAccess) {
    if (hasAccess) {
      return `${this.name} can access the internet.`;
    } else {
      return `${this.name} can't access the internet.`;
    }
  },
};

// object that needs the accessInternet method
const oldPhone = { name: "Samsung J5" };

//bind accessInternet method to another object using bind().
//and store the newly created function in a variable
const oldPhoneBound = newPhone.accessInternet.bind(oldPhone, false);
// "oldPhoneBound" function can now be called in any context and at any time,
// its `this` value would always refer to the "oldPhone" object
Enter fullscreen mode Exit fullscreen mode

You can think of bind() working like this -

const oldPhoneBound = (oldPhone) => newPhone.accessInternet.call(oldPhone);
Enter fullscreen mode Exit fullscreen mode

JavaScript creates a function for you, then uses call() to tie the object method this to the desired object. Since codes placed in a function would not run until the function is invoked, bind() makes use of that phenomenon

  1. It creates a function,

  2. In the new function body, it places a call() function that ties the object method this to the desired object and returns it

  3. The new function can then be stored in any variable and called at any particular time.

When to use bind()

For instance, when using this in a setTimeout, it sometimes doesn't work as expected because setTimeout invokes the function you provided it automatically after a given time.

When setTimeout calls an object method that uses this, the this keyword would refer to the global object because setTimeout was what called the object method, not the object. Remember, this in a function is determined by how the function is called. However, to always make sure this points to the right object, bind() comes to the rescue

function accessInternet(hasAccess) {
  console.log(this)
}

const oldPhone = { name: "Samsung J5" };
oldPhone.hasAccess = accessInternet;

setTimeout(oldPhone.hasAccess, 5000, true); // Global object.
// "oldPhone.hasAccess" was called by setTimeout, 
// `this` loses its context because...
//  oldPhone didn't call the function, setTimeout did...
// `this` defaults to the global object.


//To fix this
function accessInternet(hasAccess) {
  console.log(this)
}

const oldPhone = { name: "Samsung J5" };
//bind "this" to the desired object
oldPhone.hasAccess = accessInternet.bind(oldPhone);

setTimeout(oldPhone.hasAccess, 5000, true) //oldPhone Object

// If call() had been used, the function would have been invoked before you even get a chance to use setTimeout()
Enter fullscreen mode Exit fullscreen mode

To avoid unexpected behavior, be cautious when passing functions that use this keyword. Errors can occur if this context is not properly bound to the object it needs to reference. Binding this to the object before passing it around ensures that it always has the expected context.

Fun Fact

The bound function created by bind() can be further bound. Although no matter how many times it is rebounded, this would always point to the initial object it bounded to. You can read more on that here.

Using this In Arrow Functions

ES6 JavaScript introduced a new type of function called "Arrow Function", which can be written as let func = () => {}.

const playing = () => true;
Enter fullscreen mode Exit fullscreen mode

You've probably heard of this statement "Arrow functions don't have their own this". You may wonder, "What does this mean?".

Arrow functions don't follow the rules their sibling(i.e normal functions) follows, whereby this value is determined by how the function is called. In arrow functions, this refers to the value of its surrounding lexical scope and if none, it defaults to the global object. this in arrow function is set to what it was when the arrow function was made.

Visual representation of how

Therefore,

  • If an arrow function is created in the global scope, this value refers to the global object e.g
// Create an arrow function that returns the value of `this`
const arrowFunc = () => this;

// An object with a `name` property
const newPhone = { name: "Samsung S23 Ultra" };

// Add a method to the object that is set to `arrowFunc`
newPhone.method = arrowFunc;

// call `newPhone.Method`
console.log(newPhone.method()); //global object

// The arrow function is called,
// but since `arrowFunc` was created in the global scope,
// `this` has been binded by default to the global object,
// even though it was called as a method of the `newPhone` object.
Enter fullscreen mode Exit fullscreen mode
  • If an arrow function is used as an object method, this refers to the enclosing lexical scope of the arrow function.
const newPhone = {
  name: "Samsung S23 Ultra",
  thisValue: this,
  arrowFunc: () => this, //arrow function as object method
};

console.log(newPhone.thisValue); // Output: global object.
console.log(newPhone.arrowFunc()); // Output: global object.
// In this example, `this` in "arrowFunc"...
// is similar to `this` in "thisValue" property...
// which is the enclosing lexical scope, 
// in this case the global object.

// Remember, objects themselves do not have a scope, only functions and blocks in the object are scoped.
Enter fullscreen mode Exit fullscreen mode
  • If an arrow function is created inside a normal function, this value is determined by how the normal function is called e.g
const newPhone = {
  name: "Samsung S23 Ultra",
  arrowFuncInFunction() {
    let foo = () => this;
    // "foo" is an arrow function in a normal function

    return foo();
  },
};

console.log(newPhone.arrowFuncInFunction()); // Output: newPhone object.
// The arrow function inside "arrowFuncInFunction" method...
// returns the `this` value of its enclosing lexical scope,
// which would be determined by how "arrowFuncInFunction" is called.
Enter fullscreen mode Exit fullscreen mode
  • If an arrow function is invoked using call(), apply(), or bind(), the "thisArg" passed is discarded because the arrow function this has already been bounded to the this context of its lexical scope.
// Create an arrow function that returns the value of `this`
const arrowFunc = () => this;

// An object with a `name` property
const newPhone = { name: "Samsung S23 Ultra" };

// use call() to bind `this` to newPhone
console.log(arrowFunc.call(newPhone)); //global object

// This did not work as expected because,
// the "arrowFunc" has automatically bounded `this`...
// to its lexical scope when it was created in the global scope.
// Therefore trying to rebind it using call() would have no effect,
// the output is still the global object.
Enter fullscreen mode Exit fullscreen mode

this In Object Methods

Objects Methods in simple terms are functions that are found in objects. Let's use the Samsung phone you purchased earlier as an example, the phone is an object that has object methods(i.e functions) like the ability to play games, watch videos, chat with your ex etc.

Did you know?

Object curly braces {} are not enclosing lexical scope. They just happened to be used by JavaScript a long time ago before scoping was introduced. They do not define a new scope, only the methods in an object creates a new scope.

Now, when using this with objects properties that aren't methods, this refers to the global object, while this in object methods refers to how the object method is called.

const newPhone = {
  // object properties that aren't methods
  name: "Samsung S23 Ultra",
  thisPhone: this,
  // object method
  thisPhoneMethod() {
    return this;
  },
};

console.log(newPhone.thisPhone); // global object
console.log(newPhone.thisName()); // newPhone objet

// "newPhone.thisPhone" property points to the window object...
// because `this` can not be assigned as a value...
// "newPhone.thisPhoneMethod" points to the "newPhone" object...
// because the method was called with the newPhone object.
Enter fullscreen mode Exit fullscreen mode

Remember, objects can use the method of another object as their own method. This can be done by directly storing an object method as a property of the desired object, by prototype inheritance, or by using call, apply and bind functions.

📍 Note

Just because a function is created inside an object does not mean the value of this in that object method would always refer to the object. this in a particular object method can refer to any object that calls that object method.

Using this in callbacks

Oh callbacks, a concept in JavaScript that's also a little bit difficult to understand. Well, for those who don't know, callbacks are basically functions that can be called by another function when passed as an argument to that function, hence the name callbacks.

For example, setTimeout accepts a callback function. When setTimeout is called, it automatically invokes the function you passed as an argument at the time specified. Most in-built array methods like map, and forEach all need a callback function as an argument which they execute as soon as they are called.

In a nutshell, closure gets into a function as an argument, the function invokes the callback when called. That's all.

Now, this in callbacks are relatively simple, they depend on how the callback is called. If you try to attach an object to a callback, this would refer to the context in which the function that executes the callback is called. For example

// custom function to execute callback functions
function invokeCallback(callback){
  return callback();
}

const human = {
  name: "Eren Yeager",
  getObject() { return this },
};

invokeCallback(human.getObject); // output: global object
Enter fullscreen mode Exit fullscreen mode

Oof, not what you wanted, right?

why does this happen? Well, you passed "human.getObject" as a callback to "invokeCallback" function, and you handed over the invocation rights to "invokeCallback". Remember, this is determined by how a function is called, so since "invokeCallback" was the one that invoked the callback function, this couldn't find any object to refer to so it defaults to the global object.

To fix this, you'd have to explicitly bind this to "human" object.

function invokeCallback(callback){
  return callback();
}

const human = {
  name: "Eren Yeager",
  getObject() { return this },
};

// explicitly bind `this` to human object
// bind() returns new function
const boundHumanObject = human.getObject.bind(human);

// pass bounded function as callback for "invokeCallback"
invokeCallback(boundHumanObject); // human object

// works perfectly now.
// No matter what calls the function, `this` would always reference "human" object
Enter fullscreen mode Exit fullscreen mode

Some JavaScript inbuilt methods actually allows you to specify the this value of a callback as an argument. They allow you to pass both the callback and the this value as arguments. A good example would be the map array method:

const human = { name: "Jackie Chan" };

// callback function that set abilities to any object
function setAbilities(value) {
  return `${this.name} can ${value}`;
}

// array with abilities for an object
const abilities = ["eat", "fight", "sleep"];

// Use map() to call "setAbilities" with each item on "abilities" array.
// Pass the "human" object as second argument to map(),
// so that `this` in "setAbilities" refers to the "human" object.
const jackieAbilities = abilities.map(setAbilities, human);

console.log(jackieAbilities);
//outputs: ["Jackie Chan can eat", "Jackie Chan can fight", "Jackie Chan can sleep"]
Enter fullscreen mode Exit fullscreen mode

Other JavaScript in-built methods that accept this as an argument include:

  • Array methods - every(), filter(), find(), findIndex(), forEach(), map(), reduce(), reduceRight(), some(), apply(), bind(), call(), match(), replace(), search(), split()

  • Function methods - call(), apply() and bind()

  • TypedArray methods - every(), filter(), forEach(), map(), reduce(), reduceRight(), some()

  • Reflect methods - apply(), construct(), defineProperty(), deleteProperty(), get(), getOwnPropertyDescriptor(), getPrototypeOf(), has(), isExtensible(), ownKeys(), preventExtensions(), set(), setPrototypeOf()

Using this In Constructors

In simple terms, constructors are object builders; well, life would have been great if that was all it was, so let's dive in.

Constructors are functions used to create new objects with common properties and methods. Apart from the arrow function, any function can serve as a constructor by invoking it with the new keyword.

this in constructors would always refer to the new object that the constructor builds when invoked with new keyword. In a constructor function, this has no context until the constructor is invoked, at which point it becomes the newly created object.

The this keyword is a core feature that's needed by constructors to build objects. You can think of how constructors work like this:

function Phone(brand, model) {
  // this = {}; (new object created in private by JavaScript)
  this.brand = brand;
  this.model = model;
  // return this; (Javascript returns newly created objects by default.)
}

// When calling the constructor, 
// the 'new' keyword creates a new object
const newPhone = new Phone("Samsung", "Note 9");

// The 'new' keyword creates an empty object,
// and set `this` to the object.
// The constructor copies all properties in it to the empty objects
// by using `this` to refer to the new object.
// The new object is then returned from the constructor.
// The new "newPhone" variable now references the new object that was created.
Enter fullscreen mode Exit fullscreen mode

The majority of constructors' names begin with an uppercase letter. It isn't compulsory but just don't be that person.

🚫 Why arrow functions cannot be used as constructors

The reason why arrow functions can't be used as a constructor is because this in arrow functions are auto-bounded to the this context of its lexical scope when created. This wouldn't work for a constructor since this needs to be bound by new keyword to a newly created object which unfortunately arrow functions can't do.

Using this in class

Classes in JavaScript are like blueprints for building objects.

Developers often refer to them as synthetic syntax for function constructors because they provide a more concise and readable syntax for defining constructor functions.

In this article, we're focusing more on how this works in classes so going into the nitty gritty details of how constructor works won't be possible (It's quite broad). Take a deep dive into classes here.

Here is a simple declaration of a class compared to a function constructor

// Class Syntax
class Phone {
  constructor(brand, model) {
    this.brand = brand;
    this.model = model;
  }
  getPhoneName() { return `${this.brand} ${this.model}` }
}

//Function Syntax
function Phone(brand, model) {
  this.brand = brand;
  this.model = model;
}
Phone.prototype.getPhoneName = function() { return `${this.brand} ${this.model}` };

// In this example, `this` works the same way in both syntax.
// Although, the class syntax is much better to look at.
Enter fullscreen mode Exit fullscreen mode

When dealing with this in classes, there are two contexts you need to watch out for: Instance and Static. this has different effects in both contexts. I know, it's a lot.

Instance context

This refers to the properties and methods found in a class that objects created from that class would inherit. In this context, this refers to the object the class creates.

Constructors, instance methods, and instance fields are all part of the instance context.

Most of the time, you'd use the instance context because they're the ones that contains all what a class needs to create new objects. Now, as complicated as all these may sound, this is actually pretty straightforward in instance context.

Instance context constructors and fields use this to refer to the object generated by the class whereas instance method use "this" to refer to the object that called the method (similar to how this in object method works).

class Game {
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }
  getDetails() {
    console.log(`${this.name} is a ${this.type} game.`);
  }
}

const cod = new Game("COD", "shooting");
const streetFighter = new Game("Street Fighter", "fighting");

cod.getDetails(); // COD is a shooting game.
streetFighter.getDetails(); // Street Fighter is a fighting game
Enter fullscreen mode Exit fullscreen mode

this works as expected. When the class gets invoked with 'new' keyword(very important), a new object is created by the class and all properties found in the class constructor would be made as properties for the new object. Also, all instance methods of the class would be inherited by the new object using the prototype inheritance.

Static context

This refers to the properties and methods that's used only by the class or subclasses. In other words, objects created by classes do not have access to information found in the static context. In static context, this refers to the class itself.

Static methods, static fields and static initialization blocks are all part of the static context.

The static keyword is used to declare a method or field as part of the static context. This is a good way to differentiate methods or fields into to their respective context.

class Game {
  // instance context
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }
  getDetails() {
    console.log(`${this.name} is a ${this.type} game.`);
  }

  // static context
  static name = "Class for Game objects";
  static getName() { return this.name }
}

console.log(Game.getName()); // Class for Game objects

// create an object and try to access a static method
const cod = new Game("COD", "shooting");
console.log(cod.getName()); // error: cod.getName is not a function
Enter fullscreen mode Exit fullscreen mode

In the code snippet above, when the static method getName() is called, it returns the value of the static property name since this refers to the class when in static context.

However, when we tried to call getName() using an object created by the class, it fails because getName() is a static method, which the new object doesn't have access to.

Keep in mind that objects created by a class cannot access static properties or methods; only the class itself and its subclasses can access them.

Like I said, classes can get pretty wild if you're just starting, but no worries you learn as you go (hopefully).

❕ Important

There's also a popular method that can be found in classes that inherits properties from another class - a method called super().

super() is a method that is passed to a class constructor that inherits properties from another class constructor. this refers to the object that called super() which is basically the constructor that's about to create a new object.

Using this In Event Handlers

Events and events handlers are features in JavaScript that makes developers add interactivity to a website and as expected, this keyword also made its way there. Fortunately, using this in event handlers is really straightforward and easy to understand.

Event handlers are functions that are invoked anytime a DOM element trigges an event. The this keyword in event handlers refer to the DOM element that triggered the event. This makes it incredibly simple for the event handler to access the properties or methods of the precise DOM element that triggered the event.

A simple code example is

//--- HTML Code ---
// <button id="showButton">Hi, I show stuffs</button>
// <button id="hideButton">Hi, I hide stuffs</button>

//--- JavaScript Code ---
const button1 = document.querySelector("#showButton");
const button2 = document.querySelector("#hideButton");

//Event handler function
function handleClick() {
  console.log(this.id);
  console.log(this.innerHTML);
}

button1.addEventListener("click", handleClick);
// output: showButton
// output: Hi, I show stuffs

button2.addEventListener("click", handleClick);
// output: hideButton
// output: Hi, I hide stuffs
Enter fullscreen mode Exit fullscreen mode

In this code, two event listeners were added to two buttons. The addEventListener is set to call the handleClick function when a "click" event occurs on any of the buttons. When the event occurs, this inside the handleClick function would refer to the button element that triggered the event. The id and innerHTML of the button can then be accessed through this. Therefore, this provides an easy way to access information about the specific element that triggered the event.

Best Practices When Using this

Over the years, developers have battled with the fickle nature of this so as to find out why it doesn't work as expected in their code. So here are some helpful tips and tricks that you can use to rule over the "this" keyword:

  • this in functions is determined by how the function is invoked. Alone, this would refer to the global object (non-strict mode) or undefined (strict mode).

  • Be careful when using this in arrow functions, this in arrow functions refers to the this context of the arrow function lexical scope

  • this in global scope is the global object.

  • this in object methods refers to the object that the method is called upon, not the object that owns the method.

  • this in event handlers refers to the DOM element the event handler is attached to.

  • this is a keyword; you can access what it refers to but you can't assign it a value like how variables do e.g this = car; // error

  • Use bind, call, or apply to explicitly set the this value to a desired object of choice. However, keep in mind that these methods cannot change this in arrow functions.

  • When using this, use strict mode to avoid unexpected behavior.

  • Always use new when calling a constructor function, if you don't this would refer to the global object.


Wow, it's been a long journey. Thanks for making it to the end! I'm proud of you. If you enjoyed this article, a like would be much appreciated.

Got any questions? Drop them in the comments section, and I'll do my best to help you out.

And hey, if you have any topics or ideas you'd like me to explore, I'm all ears! I'm always available to chat and see what we can create together. Don't hesitate

Now go out there and dominate that project. You've got this!

Top comments (2)

Collapse
 
huynhtn profile image
Huynh Nguyen

I found this very helpful and easy to understand. Thank you for taking your time to share your knowledge.

Collapse
 
narendra18 profile image
narendra

its nice explanation...