EN/CH Mode
BRUCE_FE JS Interview Notes - Deep Dive into JavaScript this Keyword
In-depth explanation of JavaScript this keyword's working principles, binding rules, arrow function characteristics, and the usage scenarios and best practices of call, apply, and bind methods.
EN/CH Mode
Lazy to read articles? Then watch videos!
Basic Principles of this Keyword
In JavaScript, this is a special keyword that points to the 'context object' when a function is executed. The core principle of understanding this is: this points to whoever calls the function.
Five Binding Rules of this
- •
Default Binding
: When a function is called directly, this points to the global object (non-strict mode) or undefined (strict mode) - •
Implicit Binding
: When called as an object method, this points to the object that calls the method - •
Explicit Binding
: Using call(), apply(), bind() to explicitly specify the value of this - •
new Binding
: When a function is called with the new keyword, this points to the newly created object instance - •
Arrow Function Binding
: Arrow functions don't have their own this, they use the this from their outer scope
Detailed Explanation of this Binding Methods
1. Default Binding
When a function is called directly, this defaults to pointing to the global object (window in browsers, global in Node.js). In strict mode ('use strict'), the default binding of this is undefined.
function printThis() {
console.log(this);
}
printThis(); // window or global
// Strict mode
function strictFunction() {
'use strict';
console.log(this);
}
strictFunction(); // undefined
2. Implicit Binding
When a function is called as an object method, this points to that object. This is the most common way of binding this.
const user = {
name: 'Bruce',
age: 30,
greet() {
console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old`);
},
profile: {
title: 'Developer',
showTitle() {
console.log(`My title is ${this.title}`);
}
}
};
user.greet(); // "Hello, I'm Bruce and I'm 30 years old" (this points to user)
user.profile.showTitle(); // "My title is Developer" (this points to user.profile)
// Note: Implicit binding can be lost
const greetFn = user.greet;
greetFn(); // "Hello, I'm undefined and I'm undefined years old" (this points to global object)
3. Explicit Binding
Using call(), apply(), bind() to explicitly specify the this value when a function is executed, regardless of how the function was originally defined.
function introduce(hobby1, hobby2) {
console.log(`I'm ${this.name}, I like ${hobby1} and ${hobby2}`);
}
const person = { name: 'Bruce' };
// call: parameters passed individually
introduce.call(person, 'coding', 'reading');
// "I'm Bruce, I like coding and reading"
// apply: parameters passed as an array
introduce.apply(person, ['coding', 'reading']);
// "I'm Bruce, I like coding and reading"
// bind: returns a new function, doesn't call immediately
const boundFn = introduce.bind(person, 'coding');
boundFn('reading'); // can pass remaining parameters later
// "I'm Bruce, I like coding and reading"
4. Constructor Binding
When a function is called with the new keyword, the following steps are automatically executed, including binding this to the newly created object:
- •Create a new empty object
- •Set the object's prototype to the constructor's prototype property
- •Bind this inside the function to the newly created object
- •Execute the code in the constructor
- •If the function doesn't return another object, return this newly created object
function Person(name) {
this.name = name;
// this 指向新建立的物件
}
const bruce = new Person('Bruce');
console.log(bruce.name); // "Bruce"
// this in constructor points to newly created object instance
// new binding works even in global scope or strict mode
5. Arrow Function Binding
Arrow functions don't have their own this. They capture the this value from their lexical environment at definition time, and this value won't change regardless of how the function is called.
const obj = {
name: 'Bruce',
// Traditional function - this depends on how it's called
regularMethod() {
console.log(this.name);
},
// Arrow function - this is locked to definition environment
arrowMethod: () => {
console.log(this.name);
},
// Common usage: using arrow function inside traditional method
delayedGreeting() {
setTimeout(() => {
console.log(`Hello, ${this.name}`); // this still points to obj
}, 1000);
}
};
obj.regularMethod(); // "Bruce" (this points to obj)
obj.arrowMethod(); // undefined (this points to global environment at definition)
obj.delayedGreeting(); // outputs "Hello, Bruce" after 1 second
// Note: Arrow function's this is not affected by call/apply/bind
obj.arrowMethod.call({name: 'Not Bruce'}); // still undefined
Common Solutions to this Problems
1. Using bind Method to Fix this
In constructors or class methods, you can use bind to explicitly bind the method's this to the instance.
class Counter {
constructor() {
this.count = 0;
// Bind method's this to instance
this.increment = this.increment.bind(this);
}
increment() {
this.count++;
}
getCount() {
return this.count;
}
}
const counter = new Counter();
const incrementFn = counter.increment;
incrementFn(); // Works even when called outside the object
console.log(counter.getCount()); // 1
2. Using Arrow Functions for Automatic Binding
Arrow functions automatically capture the this value at definition time, making them a good solution for handling this issues in callback functions.
class Counter {
constructor() {
this.count = 0;
}
// Using arrow function to automatically capture outer this
increment = () => {
this.count++;
}
getCount() {
return this.count;
}
}
const counter = new Counter();
const incrementFn = counter.increment;
incrementFn(); // Arrow function maintains this pointing to counter instance
console.log(counter.getCount()); // 1
3. Using Variables to Store this
Before ES6, a common approach was to store the this value in a variable in the outer function, then use that variable in inner functions.
function UserProfile() {
this.name = 'Bruce';
this.friends = ['Alice', 'Bob'];
// Store this in a variable
const self = this;
this.showFriends = function() {
this.friends.forEach(function(friend) {
console.log(self.name + ' is friends with ' + friend);
// Use self instead of this because this here points to global in non-strict mode
});
};
}
const profile = new UserProfile();
profile.showFriends();
// "Bruce is friends with Alice"
// "Bruce is friends with Bob"
🔥 Common Interview Questions
(1) How is the value of this keyword determined? Please explain the order of determination.
The value of this is determined by a fixed priority order, from highest to lowest:
- •If it's an arrow function, this maintains the this value from its outer environment at definition time, unaffected by other rules
- •If the function is called with new, this points to the newly created object instance
- •If methods like call, apply, bind are used, this points to the specified object
- •If the function is called as an object method, this points to that object
- •If it's a regular function call, this points to the global object in non-strict mode, or undefined in strict mode
// Example analysis of this binding order
// 1. Arrow function has highest priority
const arrowFn = () => { console.log(this) };
arrowFn.call({ name: 'test' }); // Global object, call cannot change arrow function's this
// 2. new binding takes precedence over explicit binding
function Person(name) {
this.name = name;
console.log(this);
}
const boundPerson = Person.bind({ type: 'bound object' });
const person = new boundPerson('Bruce'); // { name: 'Bruce' }, ignores bind object
// 3. Explicit binding takes precedence over implicit binding
const obj = {
name: 'object',
display() { console.log(this.name); }
};
obj.display.call({ name: 'call object' }); // "call object", call overrides method's this
// 4. Implicit binding takes precedence over default binding
obj.display(); // "object", this points to obj
// 5. Default binding has lowest priority
function standalone() { console.log(this); }
standalone(); // Global object or undefined (strict mode)
(2) What are the differences and use cases of call, apply, and bind?
These three methods can all explicitly specify the this value when a function is executed, but they have significant differences in usage and behavior:
Method | Execution | Parameter Passing | Common Use Cases |
---|---|---|---|
call | Immediately executes function | Parameters passed individually | Borrowing other object methods, calling parent constructor in inheritance |
apply | Immediately executes function | Parameters passed as array | Handling scenarios with many parameters, mathematical calculations (e.g., finding array maximum) |
bind | Returns new function, doesn't execute immediately | Can preset some parameters | Event handlers, maintaining this context in callback functions |
// call example: borrowing array methods for array-like objects
function formatArgs() {
// arguments is an array-like object without join method
return Array.prototype.join.call(arguments, ' - ');
}
console.log(formatArgs('a', 'b', 'c')); // "a - b - c"
// apply example: finding array maximum
const numbers = [5, 3, 8, 1, 9, 4];
const max = Math.max.apply(null, numbers); // equivalent to Math.max(5, 3, 8, 1, 9, 4)
console.log(max); // 9
// bind example: preserving this context in event handlers
class Gallery {
constructor() {
this.images = ['img1.jpg', 'img2.jpg', 'img3.jpg'];
this.currentIndex = 0;
// using bind to fix this
document.getElementById('next').addEventListener(
'click',
this.showNext.bind(this)
);
}
showNext() {
this.currentIndex = (this.currentIndex + 1) % this.images.length;
console.log('Now showing: ' + this.images[this.currentIndex]);
}
}
(3) What is the difference between arrow functions and regular functions in terms of this binding?
Arrow functions and regular functions have the following main differences in this binding:
- •Arrow functions don't have their own this, they inherit the this value from their outer environment at definition time
- •Arrow function's this binding is fixed and cannot be changed using call, apply, bind, etc.
- •Regular function's this value depends on how it's called and can be changed through various methods
// Differences in this binding between arrow and regular functions
const obj = {
name: 'object',
regularMethod() {
console.log('Regular:', this.name);
setTimeout(function() {
console.log('Regular in timeout:', this.name); // undefined
}, 100);
},
arrowMethod() {
console.log('Arrow:', this.name);
setTimeout(() => {
console.log('Arrow in timeout:', this.name); // 'object'
}, 100);
}
};
obj.regularMethod();
obj.arrowMethod();
// Cannot change arrow function's this
const arrowFn = () => console.log(this);
arrowFn.call({ name: 'test' }); // Still points to global object
// Regular function's this can be changed
function regularFn() { console.log(this.name); }
regularFn.call({ name: 'test' }); // 'test'
(5) Determine the value of this in the following code:
// Question 1: Basic binding
const obj = {
name: 'object',
method() {
console.log(this.name);
}
};
obj.method(); // 'object' (implicit binding)
// Question 2: Method assignment
const method = obj.method;
method(); // undefined (default binding)
// Question 3: Callback function
setTimeout(obj.method, 100); // undefined (default binding)
// Question 4: Arrow function
const arrowObj = {
name: 'arrow',
method() {
setTimeout(() => {
console.log(this.name);
}, 100);
}
};
arrowObj.method(); // 'arrow' (arrow function inherits outer this)
// Question 5: Constructor
function Person(name) {
this.name = name;
this.sayHi = function() {
console.log('Hi, ' + this.name);
};
}
const person = new Person('Bruce');
person.sayHi(); // 'Hi, Bruce' (new binding)
// Answers:
// 1. this in obj.method() points to obj, because it's called as an object method (implicit binding)
// 2. this in method() points to global object (non-strict mode) or undefined (strict mode), because it's called directly (default binding)
// 3. this in setTimeout points to global object, because the callback is called directly (default binding)
// 4. this in arrow function points to arrowObj, because arrow function inherits this from outer environment (lexical binding)
// 5. this in person.sayHi() points to person instance, because it's called as a method of an object created with new (new binding)