this keyword in JavaScript
this keyword in JavaScript

Decoding "this" in JavaScript: Unraveling its Mysteries

Meenu Matharu
8 min readOct 26, 2023

--

The this keyword in JavaScript is both powerful and puzzling, often leaving developers scratching their heads. Its behavior varies across different contexts, making it essential to grasp its nuances. In this blog, we'll explore how this behaves in various situations, including functions, classes, callbacks, constructors, global context, event listeners, inline event listeners, and arrow functions. We'll also unravel the roles of bind, call, and apply in altering the this context, discuss practical applications, and delve into how this changes in strict mode.

this

In JavaScript, the behavior of the this keyword sets it apart from other programming languages. Unlike some languages, where this is determined at compile-time, in JavaScript, it’s dynamically bound at runtime based on how a function is called. This means that the value of this can vary each time the function is invoked.

1. Function Context:

In a function, this refers to the object that invokes the function. Let's dive into an example:

function greet() {
console.log(`Hello, ${this.name}!`);
}

const person = { name: 'Alice' };
person.greet = greet;

person.greet(); // Output: Hello, Alice!

Here, this inside the greet function points to the person object.

2. Class Context:

In a class, this points to the instance of the class. Consider this example:

class Robot {
constructor(name) {
this.name = name;
}

introduce() {
console.log(`I am ${this.name}, your friendly robot.`);
}
}

const myRobot = new Robot('Robo');
myRobot.introduce(); // Output: I am Robo, your friendly robot.

In the introduce method, this refers to the myRobot instance.

3. Callbacks:

this in callbacks depends on how the function is invoked. Consider the following scenario:

const button = document.getElementById('myButton');

button.addEventListener('click', function () {
console.log(this); // Output: [DOM element that triggered the event]
});

Here, this refers to the DOM element that triggered the click event.

4. Constructors:

In a constructor function, this points to the newly created instance. An example is as follows:

function Fruit(name) {
this.name = name;
}

const apple = new Fruit('Apple');
console.log(apple.name); // Output: Apple

5. Global Context:

In the global context, outside any function or object, this refers to the global object (window in a browser):

console.log(this === window); // Output: true

6. Event Listeners:

In event listeners, this often refers to the DOM element that triggered the event, as shown in the earlier callback example.

7. Inline Event Listeners:

In inline event listeners, this refers to the DOM element:

<button id="myButton" onclick="console.log(this)">Click me</button>

8. Arrow Functions:

Arrow functions behave uniquely; they inherit this from the enclosing scope. Consider the following:

const obj = {
data: 'Hello',
printData: () => {
console.log(this.data); // Output: undefined
},
};

obj.printData();

bind, call, apply

The introduction of bind, call, and apply in JavaScript addresses challenges related to controlling the this context in functions. These methods provide a way to explicitly set the value of this when invoking a function, offering more flexibility and control in certain programming scenarios.

1. bind: Creating a New Function with a Fixed this Context

The bind method allows you to create a new function with a specified this value. It doesn't invoke the function immediately but rather returns a new function that, when later invoked, will have the fixed this context.

Example:

const person = { name: 'John' };
function greet() {
console.log(`Hello, ${this.name}!`);
}
const greetPerson = greet.bind(person);
greetPerson(); // Output: Hello, John!

In this example, bind creates a new function greetPerson with this set to the person object. When greetPerson is later invoked, it uses the fixed this context.

2. call and apply: Invoking a Function with a Specified this Value

The call and apply methods allow immediate invocation of a function with a specified this value. They differ in how they handle additional arguments:

  • call: Accepts a comma-separated list of arguments.
  • apply: Accepts an array of arguments.

Example using call:

const person = { name: 'Alice' };
function greet(age) {
console.log(`Hello, ${this.name}! You are ${age} years old.`);
}
greet.call(person, 25); // Output: Hello, Alice! You are 25 years old.

In this example, call is used to invoke the greet function with this set to the person object and passing 25 as an argument.

Example using apply:

const person = { name: 'Bob' };
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
greet.apply(person, ['Hi', '!']); // Output: Hi, Bob!

Here, apply is used to invoke the greet function with this set to the person object and passing an array ['Hi', '!'] as arguments.

Importance of call, bind and apply for this

Absolutely! Understanding why bind, call, and apply are crucial in JavaScript programming involves recognizing scenarios where control over the this context is essential. Let's delve into examples illustrating their importance:

1. Resolving Context Issues in Callbacks:

Consider a scenario where you’re using a callback function within an object, and the callback loses its intended this context:

const user = {
name: 'John',
greet: function () {
setTimeout(function () {
console.log(`Hello, ${this.name}!`); // Issue: this.name is undefined
}, 1000);
},
};
user.greet();

In this case, the callback function inside setTimeout loses the reference to user, resulting in this.name being undefined. Here's where bind comes to the rescue:

javascriptCopy codeconst user = {
name: 'John',
greet: function () {
setTimeout(function () {
console.log(`Hello, ${this.name}!`);
}.bind(this), 1000);
},
};
user.greet(); // Output: Hello, John!

By using bind(this), you explicitly bind the this context within the callback, ensuring it refers to the user object.

2. Dynamic Context in Constructor Functions:

When working with constructor functions, ensuring the correct this context is crucial:

function Person(name) {
this.name = name;
this.setName = function(n){
this.name = n;
}
}
const alice = new Person('Alice');
console.log(alice.name); // Output: Alice
const setName = alice.setName; // Extracting the method
setName('Bob');
console.log(alice.name); // Issue: alice.name is still Alice

Here, when extracting the setName method, it loses its connection to alice. Using bind helps maintain the context:

function Person(name) {
this.name = name;
this.setName = function(n){
this.name = n;
}
}
const alice = new Person('Alice');
console.log(alice.name); // Output: Alice
const setName = alice.setName.bind(alice); // Using bind to maintain context
setName('Bob');
console.log(alice.name); // Output: Bob

Now, setName retains the correct this context, and alice.name is updated.

3. Parameterized Functions with call and apply:

Sometimes, you may need to invoke a function with a specific this context and pass arguments. call and apply are handy in such situations:

function greet(message) {
console.log(`${message}, ${this.name}!`);
}
const person = { name: 'Jane' };

greet.call(person, 'Hi'); // Output: Hi, Jane!
greet.apply(person, ['Hello']); // Output: Hello, Jane!

Here, call and apply enable invoking greet with this set to person and passing a message as an argument.

4. Maintaining this in Prototypal Inheritance:

When dealing with prototypal inheritance, bind ensures that methods maintain their context:

function Animal(name) {
this.name = name;
}
Animal.prototype.sayName = function () {
console.log(`My name is ${this.name}`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

const myDog = new Dog('Buddy', 'Labrador');
const sayDogName = myDog.sayName; // Losing context

sayDogName(); // Issue: this.name is undefined

To solve this issue, use bind when assigning the method:

function Animal(name) {
this.name = name;
}

Animal.prototype.sayName = function () {
console.log(`My name is ${this.name}`);
};
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const myDog = new Dog('Buddy', 'Labrador');
const sayDogName = myDog.sayName.bind(myDog); // Maintaining context
sayDogName(); // Output: My name is Buddy

By binding sayDogName to myDog, you ensure the correct this context.

Strict mode

Strict mode in JavaScript introduces a more rigid set of rules and behaviors to address common programming mistakes and improve code quality. One significant aspect affected by strict mode is the behavior of the this keyword. Let's explore various scenarios to understand how this behaves in strict mode.

1. Function Context:

In function context, strict mode affects the value of this. When a function is not a method of an object, in non-strict mode, this refers to the global object. In strict mode, this remains undefined.

'use strict';
function showThis() {
console.log(this);
}
showThis(); // Output: undefined

This helps catch potential errors where the function might inadvertently modify global variables.

2. Constructors:

In strict mode, the behavior of this in constructors is altered. Without strict mode, if a constructor is called without the new keyword, this refers to the global object. In strict mode, it remains undefined.

'use strict';
function Person(name) {
this.name = name;
}
const john = Person('John'); // Error in strict mode, `this` is undefined

This prevents accidental modification of the global object and promotes safer constructor usage.

3. Object Literal Methods:

In strict mode, when using methods within an object literal, this still refers to the object.

'use strict';
const myObject = {
logThis: function () {
console.log(this);
},
};
myObject.logThis(); // Output: { logThis: [Function] }

This ensures consistency in how this behaves within object literals.

4. Event Listeners:

In the context of event listeners, strict mode impacts the value of this. In non-strict mode, this inside an event listener function typically refers to the target element. In strict mode, it remains the same.

'use strict';
const button = document.getElementById('myButton');
button.addEventListener('click', function () {
console.log(this); // Output: [DOM element that triggered the event]
});

This maintains a consistent behavior for event handling.

5. Callbacks:

In the case of callbacks, strict mode influences how this behaves. If a callback function is not a method of an object, this is undefined in strict mode.

'use strict';
function myCallback() {
console.log(this);
}
myCallback(); // Output: undefined

This helps identify potential issues with callback functions.

6. Arrow Functions:

Arrow functions are not affected by strict mode in terms of this. Unlike regular functions, arrow functions capture the this value from their enclosing scope.

'use strict';
function showThis() {
const arrowFunction = () => {
console.log(this);
};
arrowFunction();
}
showThis(); // Output: undefined

In this example, the arrow function still logs undefined in strict mode.

In conclusion, the this keyword in JavaScript is a dynamic and context-dependent element that plays a pivotal role in defining how functions and methods behave. Its behavior varies across different scenarios, ranging from function and class contexts to callbacks, constructors, and global settings. The introduction of bind, call, and apply offers developers powerful tools to explicitly control the this context, ensuring predictability and preventing unintended errors. Understanding the nuances of this is paramount for writing clean, maintainable, and bug-free JavaScript code. Whether working with object-oriented patterns, event handling, or asynchronous operations, a mastery of this empowers developers to navigate the intricacies of the language and build robust applications. Additionally, in strict mode, this takes on a more defined role, contributing to a safer coding environment by catching potential pitfalls. Overall, a comprehensive grasp of this and its related mechanisms is fundamental for any JavaScript developer striving for excellence in their programming endeavors.

--

--

Meenu Matharu
Meenu Matharu

Written by Meenu Matharu

🚀 Passionate Frontend Developer | Storyteller on a Coding Journey 🌟 Dive deep into the world of frontend technologies like HTML, CSS, JavaScript and React

No responses yet