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 specificthis
and arguments listed outapply(thisArg, [arg1, arg2, ...])
- Call function with specificthis
and arguments as arraybind(thisArg)
- Create new function withthis
permanently set- Arrow functions
() =>
- Inheritthis
from 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)