The this Keyword
In JavaScript, this is one of the most misunderstood features because its value is not fixed when a function is written — it is decided when the function is called. The same function can see four completely different this values depending on how it is invoked. Once you internalize that this is bound by the call site (not the definition site), the rules become predictable. This page walks through the four binding rules, how arrow functions opt out of them, and the classic bugs that bite everyone.
How this is decided
When a function runs, JavaScript resolves this by checking the call site against four rules, in order of precedence (highest first):
newbinding — the function was called withnew.- Explicit binding — the function was called via
call,apply, orbind. - Implicit binding — the function was called as a method of an object.
- Default binding — none of the above;
thisfalls back to the global object (globalThis) orundefinedin strict mode.
Arrow functions sit outside this system entirely: they capture this lexically from their surrounding scope.
Default binding
A plain function call — no object, no new, no bind — uses the default binding. In strict mode (which ES modules and class bodies enable automatically), this is undefined. In sloppy mode it points at the global object.
'use strict';
function whoAmI() {
return this;
}
console.log(whoAmI()); // undefined (strict mode)
Output:
undefined
In non-strict scripts the same call would log the global object (
windowin browsers,globalThisin Node). Always prefer strict mode so accidental globalthisbecomes a loudundefinederror instead of a silent bug.
Implicit binding
When a function is called as a property of an object — obj.method() — this is the object to the left of the dot. The binding comes from the call expression, not from where the method lives.
const user = {
name: 'Ada',
greet() {
return `Hi, I'm ${this.name}`;
},
};
console.log(user.greet()); // this === user
Output:
Hi, I'm Ada
The danger is that implicit binding is fragile. The moment you detach the method from its object, the dot is gone and so is the binding:
const greet = user.greet; // detached reference
console.log(greet()); // TypeError: Cannot read properties of undefined
Explicit binding
You can force this to a specific value with call, apply, or bind. call and apply invoke the function immediately (differing only in how arguments are passed); bind returns a new function with this permanently locked.
function introduce(greeting) {
return `${greeting}, I'm ${this.name}`;
}
const dev = { name: 'Grace' };
console.log(introduce.call(dev, 'Hello')); // args listed
console.log(introduce.apply(dev, ['Hey'])); // args in an array
const boundIntro = introduce.bind(dev);
console.log(boundIntro('Welcome')); // this is fixed to dev
Output:
Hello, I'm Grace
Hey, I'm Grace
Welcome, I'm Grace
A bound function keeps its this even if later passed to another object or called as a method — bind always wins over implicit binding. See call, apply & bind for the full treatment.
new binding
Calling a function with new creates a brand-new object and sets this to that object for the duration of the constructor. The new object is returned automatically (unless the constructor explicitly returns a different object).
function Point(x, y) {
this.x = x;
this.y = y;
}
const p = new Point(3, 4);
console.log(p); // { x: 3, y: 4 }
Output:
Point { x: 3, y: 4 }
new binding has the highest precedence — even a function that was previously bind-ed will use the fresh object when called with new.
Arrow functions and lexical this
Arrow functions do not have their own this. They inherit it from the enclosing lexical scope at the time they are defined, and that binding can never be changed — call, apply, bind, and even new have no effect on an arrow’s this. This makes them ideal for callbacks that need to “see” the surrounding object.
const timer = {
label: 'tick',
start() {
setTimeout(() => {
console.log(this.label); // arrow inherits start()'s this
}, 0);
},
};
timer.start();
Output:
tick
The lost this bug
The most common this bug is passing a method as a callback. Once it is called by the host (event system, setTimeout, array iterator), it is no longer called with the dot, so implicit binding is lost.
const counter = {
count: 0,
increment() {
this.count++;
},
};
// BROKEN: setInterval calls increment() with no object
setInterval(counter.increment, 1000); // this is undefined → TypeError
// FIXED: lock this with bind...
setInterval(counter.increment.bind(counter), 1000);
// ...or wrap in an arrow that preserves the call site
setInterval(() => counter.increment(), 1000);
Binding rules at a glance
| Call form | Rule | this value |
|---|---|---|
fn() | Default | undefined (strict) / global (sloppy) |
obj.fn() | Implicit | obj |
fn.call(o) / fn.apply(o) | Explicit | o |
fn.bind(o)() | Explicit | o (locked) |
new Fn() | new | the newly created object |
() => {} | Lexical | inherited from enclosing scope |
Best Practices
- Treat
thisas a property of the call, not the function — always trace how a function is invoked, not where it was written. - Use arrow functions for callbacks inside methods so they inherit the right
thisautomatically. - Reach for
bindwhen you must pass a method as a standalone callback (e.g. to an event listener or scheduler). - Keep code in strict mode (modules and classes do this for you) so a missing
thisfails loudly asundefined. - Don’t use arrow functions as object methods that rely on
this— they capture the outer scope, not the object. - Avoid relying on default binding; it almost always signals an accidental detachment.