Let's start with the most important (and confusing) concept: the this keyword. In JavaScript, this refers to the current execution context - basically, who's running the code right now.
// Example 1: 'this' in a regular object
const person = {
name: "Alice",
sayHi: function() {
console.log("Hi! My name is " + this.name);
}
};
person.sayHi(); // Output: "Hi! My name is Alice"Here, when sayHi runs, this refers to the person object because that's who called the function.
But 'this' can be tricky! Watch what happens when we use the same function in a different context:
const person = {
name: "Alice",
sayHi: function() {
console.log("Hi! My name is " + this.name);
}
};
const justTheFunction = person.sayHi;
justTheFunction(); // Output: "Hi! My name is undefined"What happened? When we called the function directly, this was no longer bound to our person object. This is where call, apply, and bind come to help!
call lets us explicitly set what this should be when calling a function:
const person1 = { name: "Alice" };
const person2 = { name: "Bob" };
function greet() {
console.log("Hello! I'm " + this.name);
}
greet.call(person1); // Output: "Hello! I'm Alice"
greet.call(person2); // Output: "Hello! I'm Bob"You can also pass arguments after the this value:
function greetWithMessage(message) {
console.log(message + ", I'm " + this.name);
}
greetWithMessage.call(person1, "Good morning"); // Output: "Good morning, I'm Alice"apply is almost identical to call, but it takes arguments as an array:
function introduce(greeting, hobby) {
console.log(greeting + ", I'm " + this.name + " and I like " + hobby);
}
// With call:
introduce.call(person1, "Hi", "coding");
// With apply:
introduce.apply(person1, ["Hi", "coding"]);
// Both output: "Hi, I'm Alice and I like coding"Think of it this way: A for Array = Apply takes an array of arguments
While call and apply immediately call the function, bind creates a new function with this permanently set:
const person = {
name: "Alice",
sayHi: function() {
console.log("Hi! My name is " + this.name);
}
};
const functionForAlice = person.sayHi.bind(person);
// Now this will work correctly:
functionForAlice(); // Output: "Hi! My name is Alice"
// bind is permanent - this won't change the binding:
const otherPerson = { name: "Bob" };
functionForAlice.call(otherPerson); // Still outputs: "Hi! My name is Alice"Arrow functions handle this differently - they inherit this from their surrounding code:
const person = {
name: "Alice",
// Traditional function with setTimeout
sayHiLater: function() {
setTimeout(function() {
console.log("Hi! My name is " + this.name); // this.name will be undefined!
}, 1000);
},
// Arrow function with setTimeout
sayHiLaterArrow: function() {
setTimeout(() => {
console.log("Hi! My name is " + this.name); // Works correctly!
}, 1000);
}
};
person.sayHiLater(); // Output after 1s: "Hi! My name is undefined"
person.sayHiLaterArrow(); // Output after 1s: "Hi! My name is Alice"call(thisArg, arg1, arg2, ...)- Call function with specificthisand arguments listed outapply(thisArg, [arg1, arg2, ...])- Call function with specificthisand arguments as arraybind(thisArg)- Create new function withthispermanently set- Arrow functions
() =>- Inheritthisfrom surrounding code
Try this code and predict the output:
const calculator = {
value: 0,
add: function(a, b) {
return this.value + a + b;
}
};
const calculator2 = {
value: 100
};
console.log(calculator.add(5, 3)); // What's this?
console.log(calculator.add.call(calculator2, 5, 3)); // What's this?
console.log(calculator.add.apply(calculator2, [5, 3])); // What's this?
const boundAdd = calculator.add.bind(calculator2);
console.log(boundAdd(5, 3)); // What's this?Solutions:
- First log: 8 (0 + 5 + 3)
- Second log: 108 (100 + 5 + 3)
- Third log: 108 (100 + 5 + 3)
- Fourth log: 108 (100 + 5 + 3)