JavaScript Advanced #2 this Binding and Call Patterns
JavaScript’s this depends on how the function is called. In other languages, this inside a class method always refers to the instance; in JavaScript the call shape decides. This post lays out the rules in one place.
The four rules that decide this
#
There’s a priority order — top to bottom.
| Rule | Call shape | this becomes |
|---|---|---|
| 1 | new Func() | the newly created instance |
| 2 | obj.method() | obj |
| 3 | func.call(x) / apply / bind | the specified x |
| 4 | func() | global object (or undefined in strict) |
The exception that overrides all four — arrow functions ignore them all and use the this from where they were defined (covered shortly).
Rule 4 — plain call #
function show() {
console.log(this);
}
show(); // strict mode: undefined / non-strict: globalThis (window)
The simplest case. In strict mode (modules, classes, let/const from Basics #2 automatically) — undefined. In non-strict — global.
ES Modules and class bodies are auto-strict, so in modern code expect undefined.
Rule 2 — method call #
const obj = {
name: 'Curtis',
greet() {
console.log(this.name);
},
};
obj.greet(); // 'Curtis'
The object on the left of the dot becomes this. The most common case.
Detaching loses it #
This is JavaScript’s most common pitfall.
const greet = obj.greet;
greet(); // undefined (or global)
The greet variable holds only the function. Without dot-call, rule 4 applies and this disappears.
This causes accidents when passing methods as callbacks or registering them as event handlers.
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
console.log(this.count);
}
}
const c = new Counter();
button.addEventListener('click', c.increment);
// ✗ on click, this is button (or undefined)
Two fixes.
// 1. wrap with an arrow function
button.addEventListener('click', () => c.increment());
// 2. bind
button.addEventListener('click', c.increment.bind(c));Rule 3 — call / apply / bind
#
Three methods to set this explicitly.
call — call immediately
#
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
greet.call({ name: 'Curtis' }, 'hi');
// hi, Curtis
First argument is this, the rest are function arguments. Calls the function immediately.
apply — arguments as an array
#
greet.apply({ name: 'Curtis' }, ['hi']);
// hi, Curtis
Same as call but arguments come in an array. Since spread arrived, apply is rarely used directly — fn(...args) is shorter.
bind — return a new function with bound this
#
const boundGreet = greet.bind({ name: 'Curtis' });
boundGreet('hi'); // hi, Curtis
boundGreet.call({ name: 'other' }, 'hi');
// hi, Curtis ← bound this wins
bind doesn’t call — it returns a new function with this bound. Once bound, this doesn’t change no matter how it’s called. Common for callback registration.
bind for partial application of arguments too
#
function add(a, b) {
return a + b;
}
const add5 = add.bind(null, 5); // this is null, first arg pinned to 5
add5(3); // 8
add5(10); // 15
The partial-application pattern from #1 Closures is also possible with bind.
Rule 1 — new call
#
function User(name) {
this.name = name;
}
const u = new User('Curtis');
u.name; // 'Curtis'
Calling with new — creates a new object, makes it this, and auto-returns it. Class calls boil down to this.
new calls beat all other rules.
Arrow functions — the exception that breaks the rules #
Arrow functions have no this of their own. They take the this from where they’re defined.
const obj = {
name: 'Curtis',
greetRegular: function() {
console.log(this.name);
},
greetArrow: () => {
console.log(this.name); // outer this — usually undefined
},
};
obj.greetRegular(); // 'Curtis'
obj.greetArrow(); // undefined (sees the this outside the object)
Even though greetArrow is defined inside the object, the arrow’s this captures the surrounding environment (usually module top-level = undefined).
Hence — methods regular, callbacks arrow #
class Counter {
constructor() {
this.count = 0;
}
increment() { // method → regular function
setTimeout(() => { // callback → arrow
this.count++; // outer this (Counter instance) preserved
}, 100);
}
}React hook callbacks, setTimeout/setInterval, fetch’s .then — all the same pattern.
bind is ignored on arrow functions
#
const arrow = () => this;
const bound = arrow.bind({ name: 'Curtis' });
bound(); // still outer this
Arrow functions have no own this — nothing to bind. bind is silently ignored.
Common confusing cases #
1) forEach callback
#
const obj = {
prefix: '> ',
items: ['a', 'b', 'c'],
log() {
this.items.forEach(function(x) {
console.log(this.prefix + x); // ✗ this isn't obj
});
},
};forEach’s callback is a regular function — not a dot call, so it’s a plain call and rule 4 (undefined) applies. Switching to an arrow function fixes it.
log() {
this.items.forEach((x) => {
console.log(this.prefix + x); // outer this preserved
});
}You can also pass a second argument to forEach (a thisArg).
this.items.forEach(function(x) {
console.log(this.prefix + x);
}, this); // second arg is thisArg
After arrow functions arrived, the thisArg pattern is rarely used.
2) DOM event handlers #
button.addEventListener('click', function(e) {
console.log(this); // the element the event is on (currentTarget)
});
button.addEventListener('click', (e) => {
console.log(this); // outer (usually undefined or window)
});In old code this was meant to point at the element where the event was attached. Arrow functions break that intent. For the element in DOM handlers, use e.currentTarget — safe regardless of function form.
this and strict mode
#
A trap from old JavaScript.
function f() {
console.log(this); // window or global
}
f();'use strict';
function f() {
console.log(this); // undefined
}
f();ES Modules, class bodies, and ES2015+ code are auto-strict. In modern code, expect this to be undefined on plain calls.
Wrap-up #
What we covered:
thisis decided by call style — 4 rules- Plain call → undefined / method call → left of dot / call,apply,bind → explicit / new → new instance
- Detaching a method to use as a callback loses
this call/applycall immediately;bindreturns a bound new function- Arrow functions have no own
this— captured from outside - Methods as regular functions, callbacks as arrows (most common guideline)
- For a DOM element in a handler,
e.currentTargetis safest
In the next post (#3 Prototype Chain) we cover what classes really are — how prototypes and the chain work.