Advanced JavaScript (ES6+)

The 'this' Keyword

13 min Lesson 13 of 40

The 'this' Keyword

The this keyword is one of the most confusing yet powerful features in JavaScript. Understanding how this works is essential for mastering JavaScript, especially when working with objects, classes, and callbacks. In this lesson, we'll demystify this and learn how to control its value.

What is 'this'?

In JavaScript, this is a special keyword that refers to the context in which a function is executed. The value of this is determined by how a function is called, not where it's defined.

Key Concept: this is not a variable - it's a keyword. Its value is set automatically by JavaScript based on the execution context.

'this' in Global Scope

In the global scope (outside any function), this refers to the global object:

console.log(this); // In browser: Window object // In Node.js: global object // Creating a global variable this.globalVar = "I'm global"; console.log(window.globalVar); // "I'm global" (in browser) // Global function function globalFunction() { console.log(this); // Window object (in non-strict mode) } globalFunction();
Important: In strict mode ('use strict'), this in global functions is undefined, not the global object. This prevents accidental global variable creation.

'this' in Object Methods

When a function is called as a method of an object, this refers to that object:

const person = { name: "John", age: 30, greet: function() { console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old`); }, birthday: function() { this.age++; console.log(`Happy birthday! Now I'm ${this.age}`); } }; person.greet(); // Hi, I'm John and I'm 30 years old person.birthday(); // Happy birthday! Now I'm 31 // 'this' refers to the person object console.log(person.age); // 31

Losing 'this' Context

A common problem occurs when you lose the this context:

const person = { name: "John", greet: function() { console.log(`Hi, I'm ${this.name}`); } }; person.greet(); // Hi, I'm John // Problem: Losing context when assigning to a variable const greetFunction = person.greet; greetFunction(); // Hi, I'm undefined (this is Window/undefined) // Problem: Losing context in callbacks setTimeout(person.greet, 1000); // Hi, I'm undefined

This happens because when you reference person.greet without calling it immediately, you lose the object context. The function is called in a different context where this doesn't refer to person.

'this' in Arrow Functions

Arrow functions don't have their own this. They inherit this from the enclosing lexical scope:

const person = { name: "John", hobbies: ["reading", "coding", "gaming"], // Regular function showHobbiesRegular: function() { this.hobbies.forEach(function(hobby) { // Problem: 'this' is undefined here console.log(`${this.name} likes ${hobby}`); }); }, // Arrow function solution showHobbiesArrow: function() { this.hobbies.forEach((hobby) => { // 'this' refers to person object console.log(`${this.name} likes ${hobby}`); }); } }; person.showHobbiesRegular(); // undefined likes reading (error) person.showHobbiesArrow(); // John likes reading, John likes coding...
Best Practice: Use arrow functions for callbacks when you want to preserve the outer this context. Use regular functions when you need a dynamic this that depends on how the function is called.

The call() Method

The call() method allows you to explicitly set the value of this and call the function immediately:

function introduce(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } const person1 = { name: "John" }; const person2 = { name: "Jane" }; // Call introduce with person1 as 'this' introduce.call(person1, "Hello", "!"); // Hello, I'm John! // Call introduce with person2 as 'this' introduce.call(person2, "Hi", "."); // Hi, I'm Jane. // Using call() to borrow methods const numbers = { data: [1, 2, 3, 4, 5] }; const stats = { data: [10, 20, 30], getSum: function() { return this.data.reduce((sum, num) => sum + num, 0); } }; console.log(stats.getSum()); // 60 console.log(stats.getSum.call(numbers)); // 15 (using numbers.data)

The apply() Method

The apply() method is similar to call(), but takes arguments as an array:

function introduce(greeting, punctuation) { console.log(`${greeting}, I'm ${this.name}${punctuation}`); } const person = { name: "John" }; // Using call() - arguments passed individually introduce.call(person, "Hello", "!"); // Using apply() - arguments passed as array introduce.apply(person, ["Hello", "!"]); // Practical example: Finding max number const numbers = [5, 6, 2, 3, 7, 1, 9, 4, 8]; // Math.max doesn't accept an array, but we can use apply const max = Math.max.apply(null, numbers); console.log(max); // 9 // Modern alternative with spread operator const maxModern = Math.max(...numbers); console.log(maxModern); // 9

The bind() Method

The bind() method creates a new function with a fixed this value. Unlike call() and apply(), it doesn't invoke the function immediately:

const person = { name: "John", greet: function() { console.log(`Hi, I'm ${this.name}`); } }; // Problem without bind setTimeout(person.greet, 1000); // Hi, I'm undefined // Solution: bind 'this' to person setTimeout(person.greet.bind(person), 1000); // Hi, I'm John // Creating a bound function const greetJohn = person.greet.bind(person); greetJohn(); // Hi, I'm John (works anywhere) // Bind with arguments function multiply(a, b) { return a * b; } const double = multiply.bind(null, 2); // Fix first argument to 2 console.log(double(5)); // 10 (2 * 5) console.log(double(10)); // 20 (2 * 10) const triple = multiply.bind(null, 3); console.log(triple(5)); // 15 (3 * 5)
Remember: bind() returns a new function, while call() and apply() invoke the function immediately.

'this' in Event Handlers

In event handlers, this typically refers to the element that triggered the event:

// HTML: <button id="myButton">Click me</button> const button = document.getElementById('myButton'); // Regular function: 'this' is the button element button.addEventListener('click', function() { console.log(this); // <button id="myButton"> this.textContent = "Clicked!"; this.style.backgroundColor = "green"; }); // Arrow function: 'this' is from outer scope button.addEventListener('click', () => { console.log(this); // Window object (not the button!) // this.textContent would cause an error }); // Using bind to set custom 'this' const handler = { clickCount: 0, handleClick: function(event) { this.clickCount++; console.log(`Button clicked ${this.clickCount} times`); console.log(`Element:`, event.currentTarget); } }; button.addEventListener('click', handler.handleClick.bind(handler));

'this' in Classes

In ES6 classes, this refers to the instance of the class:

class Counter { constructor(initialValue = 0) { this.count = initialValue; } increment() { this.count++; console.log(`Count: ${this.count}`); } decrement() { this.count--; console.log(`Count: ${this.count}`); } // Problem: Losing 'this' in callbacks startAutoIncrement() { setInterval(function() { this.increment(); // Error: this is undefined }, 1000); } // Solution 1: Arrow function startAutoIncrementArrow() { setInterval(() => { this.increment(); // Works! Arrow function preserves 'this' }, 1000); } // Solution 2: bind startAutoIncrementBind() { setInterval(function() { this.increment(); }.bind(this), 1000); } } const counter = new Counter(10); counter.increment(); // Count: 11 counter.startAutoIncrementArrow(); // Starts auto-incrementing

Constructor Functions and 'this'

When using constructor functions, this refers to the newly created object:

function Person(name, age) { // 'this' refers to the new object being created this.name = name; this.age = age; this.greet = function() { console.log(`Hi, I'm ${this.name}`); }; } const john = new Person("John", 30); const jane = new Person("Jane", 25); john.greet(); // Hi, I'm John jane.greet(); // Hi, I'm Jane console.log(john.name); // John console.log(jane.age); // 25

Common 'this' Pitfalls and Solutions

Here are the most common problems with this and their solutions:

// Pitfall 1: Method extracted from object const user = { name: "John", sayName: function() { console.log(this.name); } }; const sayName = user.sayName; sayName(); // undefined // Solution: Use bind const boundSayName = user.sayName.bind(user); boundSayName(); // John // Pitfall 2: Callback functions class Timer { constructor() { this.seconds = 0; } start() { // Wrong: loses 'this' // setInterval(this.tick, 1000); // Solution 1: Arrow function setInterval(() => this.tick(), 1000); // Solution 2: Bind // setInterval(this.tick.bind(this), 1000); } tick() { this.seconds++; console.log(`${this.seconds} seconds`); } } // Pitfall 3: Nested functions const obj = { value: 42, method: function() { console.log(this.value); // 42 function innerFunction() { console.log(this.value); // undefined (wrong 'this') } innerFunction(); // Solution: Arrow function const innerArrow = () => { console.log(this.value); // 42 (correct 'this') }; innerArrow(); } };

Practice Exercise:

Challenge: Create a BankAccount class with methods that properly handle this. Include a method that uses a callback (like setTimeout) without losing the this context.

Solution:

class BankAccount { constructor(owner, balance = 0) { this.owner = owner; this.balance = balance; this.transactions = []; } deposit(amount) { if (amount > 0) { this.balance += amount; this.transactions.push({ type: 'deposit', amount: amount, date: new Date() }); console.log(`Deposited $${amount}. New balance: $${this.balance}`); } } withdraw(amount) { if (amount > 0 && amount <= this.balance) { this.balance -= amount; this.transactions.push({ type: 'withdraw', amount: amount, date: new Date() }); console.log(`Withdrew $${amount}. New balance: $${this.balance}`); } else { console.log("Insufficient funds"); } } // Method with callback - using arrow function to preserve 'this' scheduleDeposit(amount, delay) { console.log(`Deposit of $${amount} scheduled in ${delay}ms`); setTimeout(() => { this.deposit(amount); // 'this' correctly refers to BankAccount }, delay); } getBalance() { return this.balance; } getStatement() { console.log(`Account owner: ${this.owner}`); console.log(`Current balance: $${this.balance}`); console.log(`Transactions: ${this.transactions.length}`); } } const account = new BankAccount("John Doe", 1000); account.deposit(500); account.withdraw(200); account.scheduleDeposit(100, 2000); // Deposits after 2 seconds account.getStatement();

Summary

In this lesson, you learned:

  • this is determined by how a function is called, not where it's defined
  • In global scope, this refers to the global object (Window in browsers)
  • In object methods, this refers to the object
  • Arrow functions inherit this from their lexical scope
  • call() and apply() invoke functions with a specific this
  • bind() creates a new function with a fixed this value
  • Event handlers set this to the target element
  • Classes and constructors use this to refer to instances
  • Common pitfalls and solutions for maintaining proper this context
Next Up: In the next lesson, we'll dive into Promises - the foundation of modern asynchronous JavaScript!

ES
Edrees Salih
21 hours ago

We are still cooking the magic in the way!