Difficulty: Intermediate
The `this` keyword in JavaScript is one of the most confusing aspects of the language because, unlike most other languages where `this` always refers to the current instance, JavaScript's `this` is determined dynamically at call time based on how a function is invoked. The same function can have different `this` values depending on the calling context. This is fundamentally different from lexical scoping where the context is determined by where code is written.
There are four main rules that determine what `this` refers to, evaluated in order of precedence. The first is default binding: when a function is called as a standalone function (not as a method, not with new, and not with call/apply/bind), `this` defaults to the global object (window in browsers, global in Node.js). In strict mode, the default binding sets `this` to undefined instead, which helps catch accidental global variable creation.
The second rule is implicit binding: when a function is called as a method of an object (using dot notation like obj.method()), `this` is set to the object that owns the method. However, this binding is easily lost. If you extract the method from the object and call it standalone, or pass it as a callback, the implicit binding is lost and default binding takes over. This is one of the most common sources of bugs involving `this`.
The third rule is explicit binding using call(), apply(), or bind(). These methods let you manually specify what `this` should be. call and apply invoke the function immediately with the specified `this`, while bind returns a new function with `this` permanently set. The fourth rule is the `new` binding: when a function is called with the `new` keyword, `this` is set to the newly created object. This is how constructor functions and classes work.
Arrow functions introduced in ES6 are a special case that deliberately break from these rules. Arrow functions do not have their own `this` binding. Instead, they inherit `this` from their enclosing lexical scope at the time they are defined. This makes them ideal for callbacks and event handlers where you want to preserve the `this` value of the surrounding code. However, it also means arrow functions should not be used as methods on objects or as constructors.
In event handlers in the browser, `this` refers to the DOM element that the event listener is attached to (when using regular functions). This is an example of implicit binding -- the browser calls the handler as a method of the element. If you need to access a different `this` inside an event handler (for example, a class instance), you can use an arrow function or bind the handler in advance.
// 1. Default Binding
function showThis() {
'use strict';
console.log('Default:', this);
}
showThis(); // undefined (strict mode)
// 2. Implicit Binding
const user = {
name: 'Alice',
greet() {
console.log('Implicit:', this.name);
}
};
user.greet(); // 'Alice'
// 3. Explicit Binding
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
const bob = { name: 'Bob' };
introduce.call(bob, 'Hello'); // Explicit via call
// 4. new Binding
function Person(name) {
this.name = name;
}
const charlie = new Person('Charlie');
console.log('new:', charlie.name);
Each binding rule produces a different `this` value. Default binding gives undefined (strict mode) or global object. Implicit binding uses the calling object. Explicit binding lets you choose. The new keyword creates a fresh object.
const calculator = {
value: 100,
getValue() {
return this.value;
}
};
// Works -- implicit binding
console.log(calculator.getValue()); // 100
// Broken -- binding is lost when method is extracted
const getValue = calculator.getValue;
console.log(getValue()); // undefined
// Broken -- binding is lost when passed as callback
function executeCallback(cb) {
return cb();
}
console.log(executeCallback(calculator.getValue)); // undefined
// Fix with bind
const boundGetValue = calculator.getValue.bind(calculator);
console.log(boundGetValue()); // 100
console.log(executeCallback(boundGetValue)); // 100
When you assign a method to a variable or pass it as a callback, the implicit binding to the object is lost. The function is now called as a standalone function, so `this` falls back to default binding (undefined in strict mode). Using bind() creates a new function with `this` permanently set.
const team = {
name: 'Engineering',
members: ['Alice', 'Bob', 'Charlie'],
// Regular function -- `this` is the team object
listMembersRegular() {
console.log(`Team: ${this.name}`);
// Regular function callback -- `this` is lost!
this.members.forEach(function(member) {
// `this` is undefined here (strict) or global
console.log(` ${member} -> ${this?.name || 'LOST'}`);
});
},
// Arrow function fixes it
listMembersArrow() {
console.log(`Team: ${this.name}`);
// Arrow function inherits `this` from listMembersArrow
this.members.forEach((member) => {
console.log(` ${member} -> ${this.name}`);
});
}
};
team.listMembersRegular();
console.log('---');
team.listMembersArrow();
In listMembersRegular, the forEach callback is a regular function, so it gets its own `this` (default binding). In listMembersArrow, the forEach callback is an arrow function that inherits `this` from the enclosing method, preserving access to the team object.
// Precedence: new > explicit > implicit > default
function Foo(name) {
this.name = name;
}
const obj1 = { name: 'obj1' };
// bind sets this to obj1
const BoundFoo = Foo.bind(obj1);
BoundFoo('bound-call');
console.log(obj1.name); // 'bound-call'
// new overrides bind!
const obj2 = new BoundFoo('new-call');
console.log(obj2.name); // 'new-call' (new wins)
console.log(obj1.name); // 'bound-call' (unchanged)
// Pitfall: arrow function as object method
const badObj = {
value: 42,
getValue: () => {
// `this` is NOT badObj -- it's the outer scope!
return this?.value;
}
};
console.log(badObj.getValue()); // undefined
The `new` keyword has the highest precedence and overrides even bind(). Arrow functions used as object methods are a common mistake -- they inherit `this` from where the object literal is evaluated, not from the object itself.
Default Binding, Implicit Binding, Explicit Binding, new Binding, Arrow Functions, this in Event Handlers