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!