Exploring JavaScript's 'this' Behavior

Exploring JavaScript's 'this' Behavior

The this keyword refers to different objects depending on how it is used.

this in global scope

If this is called or used in a global scope it refers to window object in browser. In respective JS environment it depends on respective global object.

console.log(this); // global object (window in browser environment)

this inside a Function

It depends on how the function is called ?

Strict vs Non-Strict

Firstly we will see how this behave inside a regular function in "strict" mode and "non-strict" mode.

The value of this is "undefined" in strict mode because default binding is not allowed.

'use strict';

function greet() {
    console.log(this);
}

greet(); // undefined

The value of this in non-strict mode follows a process called substitution which ensures value is object. If a function is invoked with this explicitly set to "undefined" or "null", it is automatically bound to the global object, which is referred to as globalThis in modern JavaScript environments.

Let us see what is globalThis ?

The global object has different names in different environments. This creates a problem when we try to run the same JavaScript code in different environments. This is where globalThis property comes into the picture.

The globalThis is a global variable, which contains global this object. The global this object always points to the global object irrespective of whether the code runs in the browser, Node JS, or in any other JavaScript environment.

Hence we can always access the global object using the globalThis variable.

function greet() {
    console.log(this);
}

greet(); // global object (window in browser environment)

Arrow Function

Now we will see how this behave differently with respect to arrow function.

In arrow function this don't have their own context, the value of this keyword inside arrow function is defined by the parent scope irrespective of any mode.

(() => {
    console.log(this); // global object (window in browser environment)
})()

this inside a Object

Now we will see how this behave in method declared inside an object.

In object method, this refers to the object itself and if its arrow function it refer to parent scope not object.

const restaurant = {
    name: 'Delicious Diner',
    owner: 'Murali',
    isopen: true,
    greet: function() {
        console.log(`Welcome to ${this.name}, owned by ${this.owner}.`);
    },
    serveDish: dish => {
        console.log(`Serving ${dish} at ${this.name}.`);
    },
    kitchen: {
        chefs: ['Chef 1', 'Chef 2', 'Chef 3'],
        prepareDish: function(dish) {
            console.log(`${this.chefs[0]} is preparing ${dish} in the kitchen of ${this.name}.`);
        },
        cleanup: () => {
            console.log(`Cleaning up kitchen of ${this.name}.`);
        }
    },
    announce: function() {
        const message = 'Welcome to our restaurant!';
        const announceMessage = () => {
            console.log(`${message} ${this.name}`);
        };
        announceMessage();
    }
};

// Call methods
restaurant.greet(); // Welcome to Delicious Diner, owned by Murali.
restaurant.serveDish('Pizza'); // Serving Pizza at .
restaurant.kitchen.prepareDish('Spaghetti'); // Chef 1 is preparing Spaghetti in the kitchen of undefined.
restaurant.kitchen.cleanup(); // Cleaning up kitchen of .
restaurant.announce(); // Welcome to our restaurant! Delicious Diner.

Explanation :

1. restaurant.greet() is a regular function, therefore this refers refers to object restaurant.

2. restaurant.serveDish('Pizza') and restaurant.kitchen.cleanup() is a arrow function, therefore this refers to the parent scope (global scope) which is window object and in window there is no "name" present.

3. restaurant.kitchen.prepareDish('Spaghetti') is a regular function, therefore this points to the kitchen property of the restaurant object which holds an object. "name" is not defined inside object.

4. In restaurant.announce() function, announceMessage is arrow function which is declared, initialized and called. Here this points to the parent scope (restaurant object) as it is nested within a regular function where this refers to the restaurant object.

When assigning this as a value to an object property, it references what is defined in the parent scope, independent of the object.

const obj = {
    name: this
};
console.log(obj.name); // global object (window in browser environment)

If we use object methods with other object as argument via call(), apply() , bind() , or Reflect.apply() it refers to the object that is passed as value for this.

const burgerKingRestaurant = {
    name: 'Burger King',
    owner: 'Yuvraj'
};

// Using call and apply to invoke the greet method of restaurant with burgerKingRestaurant. 
restaurant.greet.call(burgerKingRestaurant); // Welcome to Burger King, owned by Yuvraj. 
restaurant.greet.apply(burgerKingRestaurant); // Welcome to Burger King, owned by Yuvraj.

// Using bind to create a new function with the greet method of restaurant bound to burgerKingRestaurant
const boundGreet = restaurant.greet.bind(burgerKingRestaurant);
boundGreet(); //Welcome to Burger King, owned by Yuvraj.

// Using Reflect.apply to invoke the greet method of restaurant with burgerKingRestaurant 
Reflect.apply(restaurant.greet, burgerKingRestaurant, []); // Welcome to Burger King, owned by Yuvraj.

this inside a Class

Classes are always in strict mode.

this with super when passing parameters: When calling super with parameters in a subclass constructor, the parameters are passed to the constructor of the parent class. This means that the parent class constructor is executed with the provided parameters, and any variables or properties defined within the parent constructor are initialized accordingly.

this when calling parent methods with super: When calling parent methods within a subclass using super, this still refers to the instance of the subclass, not the parent class. This means that even though you're invoking a method defined in the parent class, this within that method still points to the instance of the subclass.

class Restaurant {
    constructor(name, owner) {
        this.name = name;
        this.owner = owner;
    }
    greetCustomer() {
        console.log(`Welcome to ${this.name}, owned by ${this.owner}.`);
    }
}

class FastFood_Restaurant extends Restaurant {
    constructor(name, owner, franchise) {
        super(name, owner);
        this.franchise = franchise;
    }
    greetCustomer() {
        super.greetCustomer();
        console.log(`Enjoy your meal at ${this.name} (${this.franchise}).`);
    }
}

const burgerPlace = new FastFood_Restaurant('Burger Queen', 'Shweta', 'International Franchise');
burgerPlace.greetCustomer();
// Welcome to Burger Queen, owned by Shweta.
// Enjoy your meal at Burger Queen (International Franchise).

this in DOM event handlers

When directly assigning a function as an event handler to a DOM element, such as using the onclick attribute in HTML or element.onclick property, this is automatically bound to the element itself. However, exceptions exist for dynamically added event listeners using methods other than addEventListener(), where this binding may not occur as expected in older browsers or certain non-standard environments.

// Create a button element
const button = document.createElement('button');
button.textContent = 'Click me';

// Append the button to the document body
document.body.appendChild(button);

// Case 1: Using regular function as an event handler using addEventListener()
button.addEventListener('click', function() {
    console.log('Case 1 addEventListener():');
    console.log(this); // <button>Click me</button>
});

// Case 2: Using arrow function as event handler with addEventListener()
button.addEventListener('click', () => {
    console.log('Case 2 Arrow function with addEventListener():');
    console.log(this); // global object (window in browser environment)
});

// Case 3: Using a method of an object as an event handler
const obj = {
    handleClick: function() {
        console.log('Case 3 Object method as event handler:');
        console.log(this); // <button>Click me</button>
    }
};
button.addEventListener('click', obj.handleClick);

// Case 4: Using a function directly assigned to the onclick property
button.onclick = function() {
    console.log('Case 4 onclick property:');
    console.log(this); // In modern browsers, we see <button>Click me</button> but in older browsers, particularly versions 
    // of Internet Explorer, there may be inconsistencies, and this might refer to the global object (window in browser 
    // environment) instead of the element. 
};

From regular functions to arrow functions, and from objects to classes, including DOM event handlers, we've unveiled the dynamic behavior of 'this' in JavaScript, offering a glimpse into its versatile nature across various contexts.