JavaScript this and Function Binding
Demystify the JavaScript this keyword with clear rules, arrow vs normal functions, bind/call/apply, class methods, and the bugs that catch every developer.
What you'll learn
- ✓The four rules that determine what this points to
- ✓Why arrow functions do not have their own this
- ✓How call, apply, and bind explicitly set this
- ✓How to keep this stable in class methods and event handlers
- ✓The most common this bugs and their fixes
Prerequisites
- •Solid grasp of JavaScript functions
- •Working knowledge of arrow functions
this is the most misunderstood word in JavaScript, and most of the confusion comes from people trying to memorize every case instead of learning the small set of rules that produces them. Once you internalize how this is resolved at call time, the language stops feeling magical and starts feeling consistent.
The single most important idea
In most programming languages, this is determined when a method is defined. In JavaScript, this is determined when a function is called. The same function can have a different this on every invocation, because what matters is how you call it, not how you wrote it.
That is the rule. Everything that follows is a special case of it.
The four call patterns
There are four ways to call a normal function, and each one sets this differently.
1. Method call
When you call a function as a property of an object, this is that object.
const user = {
name: "Ada",
greet() {
console.log(`Hi, ${this.name}`);
},
};
user.greet(); // "Hi, Ada"
The dot before greet is doing the work. Whatever is to the left of the dot becomes this.
2. Standalone call
When you call a function with no object in front of it, this is undefined in strict mode and the global object otherwise. ES modules and class bodies are always strict, so in practice you get undefined.
"use strict";
function show() {
console.log(this);
}
show(); // undefined
This is the source of the classic “lost this” bug. Pull the method off the object and you lose the binding:
const greet = user.greet;
greet(); // TypeError: Cannot read properties of undefined
3. Constructor call
When you call a function with new, JavaScript creates a fresh object and binds this to it.
function Point(x, y) {
this.x = x;
this.y = y;
}
const p = new Point(1, 2);
p.x; // 1
4. Explicit call
call, apply, and bind let you set this to whatever you want.
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
greet.call({ name: "Ada" }, "Hi"); // "Hi, Ada"
greet.apply({ name: "Ada" }, ["Hello"]); // "Hello, Ada"
const bound = greet.bind({ name: "Ada" });
bound("Hey"); // "Hey, Ada"
The only difference between call and apply is how they take arguments. bind returns a new function with this permanently fixed.
Arrow functions are different
Arrow functions do not have their own this. They inherit it from the surrounding lexical scope, the same way they inherit any other variable. You cannot rebind an arrow function’s this with call, apply, or bind. You also cannot use new with one.
const user = {
name: "Ada",
greet: () => {
console.log(this.name);
},
};
user.greet(); // undefined
Why undefined? Because the arrow function was defined at the top level of the file, where this is undefined in strict mode. The dot in user.greet() does not matter for arrows.
This is exactly what you want for callbacks though:
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds += 1;
console.log(this.seconds);
}, 1000);
}
}
new Timer().start();
The arrow function captures this from start, where this is the Timer instance. If you used a normal function inside setInterval, it would lose the binding and this.seconds would crash.
Methods on classes
A class method is just a property on the prototype. It follows the method call rule, which means it has the same lost-this problem as plain objects.
class Counter {
constructor() { this.n = 0; }
inc() { this.n += 1; }
}
const c = new Counter();
const inc = c.inc;
inc(); // TypeError
There are three idiomatic fixes.
Bind in the constructor:
class Counter {
constructor() {
this.n = 0;
this.inc = this.inc.bind(this);
}
inc() { this.n += 1; }
}
Use a class field with an arrow function:
class Counter {
n = 0;
inc = () => { this.n += 1; };
}
Or wrap at the call site:
button.addEventListener("click", () => c.inc());
The arrow-as-class-field version is the most common in modern code. It costs you one closure per instance but reads cleanly and eliminates the entire class of bugs.
call and apply for borrowing methods
A common use of call is borrowing a method from one object to use on another. Array-like objects, for example, do not have array methods, but you can borrow them.
function logArgs() {
const args = Array.prototype.slice.call(arguments);
console.log(args);
}
logArgs(1, 2, 3); // [1, 2, 3]
In modern code you would write [...arguments] or use rest parameters, but the pattern still appears in older codebases and library internals.
bind for partial application
bind does more than fix this. It can also preset arguments.
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
double(5); // 10
double(7); // 14
The null says you do not care about this, and 2 becomes the first argument of every call. This is a tiny version of currying that works without any libraries. For more on the function side of this, see JavaScript functions.
Common this bugs and their fixes
The lost method. You pass a method as a callback and it loses its binding.
// Broken
btn.addEventListener("click", user.greet);
// Fixed
btn.addEventListener("click", () => user.greet());
The forgotten new. You call a constructor without new and end up writing properties to the global object or getting a TypeError in strict mode.
// Modern fix: use class syntax which always throws if called without new
class Point {
constructor(x, y) { this.x = x; this.y = y; }
}
The arrow method. You define a method as an arrow function and this is not the instance.
const user = {
name: "Ada",
// BAD: arrow ignores user
greet: () => console.log(this?.name),
// GOOD: shorthand method is a normal function
greetCorrectly() { console.log(this.name); },
};
The nested function. You define a regular function inside a method and lose this when you call it.
class List {
constructor(items) { this.items = items; }
print() {
this.items.forEach(function (item) {
console.log(this.name, item); // this is undefined
});
}
}
Fix it by using an arrow function as the callback. This is one of the original motivations for arrow functions, and it works beautifully alongside array methods.
Wrap up
this is not magic, it is a parameter that gets filled in at call time according to four rules: method call, standalone call, new, and explicit binding with call/apply/bind. Arrow functions opt out of all of that and use the surrounding this instead. When you read code, look at how a function is being called, not how it was written. When you write code, prefer arrow functions for callbacks, class fields for methods you will pass around, and normal methods for everything else. Follow that and you will almost never have a this bug again.