Introduction
The value of this in JavaScript can be confusing because it does not always mean “the object containing the function.” For a regular function, the way the function is called usually decides the value of this. An arrow function follows a different rule: it keeps the this value from the surrounding code where it was created.
For example, the same regular function can be used by two different objects:
"use strict";
function identify() {
return this?.name ?? "no receiver";
}
const first = { name: "first", identify };
const second = { name: "second", identify };
console.log(first.identify()); // "first"
console.log(second.identify()); // "second"
Both objects refer to the same function. However, the first call gives first as this, while the second gives second. This is why it is more useful to ask how was the function called? instead of asking where it was written.
A more reliable way to reason about this begins with one question:
For a regular function, how was the function called?
Regular functions generally receive this from the call site. Arrow functions are different: they do not create their own this value. Instead, they keep the this value from the surrounding code. This is sometimes called lexical this. “Lexical” simply means that the value comes from where the arrow was written, rather than from the later call that invokes it.
Regular functions use the call site
The location of a regular function in a source file does not permanently attach it to an object. The same function can therefore be called with different receivers. A receiver is the object used before the dot in a method call, such as account in account.show():
"use strict";
function identify() {
return this?.name ?? "no receiver";
}
const first = { name: "first", identify };
const second = { name: "second", identify };
console.log(first.identify()); // "first"
console.log(second.identify()); // "second"
Both properties refer to the same function object. The expression before the call's parentheses determines the receiver: first in first.identify() and second in second.identify().
An arrow function has no equivalent dynamic binding:
function makeIdentifier() {
return () => this.name;
}
const identifyFirst = makeIdentifier.call({ name: "first" });
console.log(identifyFirst());
// "first"
The regular function makeIdentifier receives this through call(). The arrow is then created inside that invocation and captures the same this. Calling the returned arrow later does not choose a new receiver.
Call forms compared
| Call form | Example | this in a regular function |
this in an arrow function |
|---|---|---|---|
| Plain call | fn() |
undefined in strict mode; usually globalThis after substitution in non-strict mode |
Captured from the surrounding lexical scope |
| Method call | object.fn() |
object, the receiver used at the call site |
Still the surrounding lexical this; the object does not become its receiver |
| Extracted method | const fn = object.method; fn() |
The method relationship is lost; this is now a plain call | Unchanged because arrows do not use a dynamic receiver |
| Explicit call | fn.call(value, a) |
value, subject to non-strict substitution rules |
The supplied receiver is ignored |
| Explicit apply | fn.apply(value, [a]) |
value, subject to non-strict substitution rules |
The supplied receiver is ignored |
| Bound call | fn.bind(value)() |
The bound function uses value |
Binding cannot replace the arrow's captured this |
| Constructor call | new Fn() |
The newly created instance, unless another object is explicitly returned | Not allowed; arrows are not constructors |
| Callback | api(fn) |
Whatever receiver the API supplies when it invokes fn |
The this captured where the arrow was created |
The table describes the principal rule, not every exotic case. Bound constructors, proxies, getters, DOM event handlers, and host APIs add details. For ordinary application code, classifying a function as regular, arrow, or bound and then inspecting its call site resolves most questions.
Output prediction 1: plain, method, extracted, and explicit calls
Before reading the explanation, predict the four lines printed by this program:
"use strict";
function showLabel(prefix) {
return `${prefix}: ${this?.label ?? "no receiver"}`;
}
const item = {
label: "Keyboard",
showLabel,
};
const extracted = item.showLabel;
console.log(showLabel("plain"));
console.log(item.showLabel("method"));
console.log(extracted("extracted"));
console.log(extracted.call({ label: "Mouse" }, "call"));
The verified output is:
plain: no receiver
method: Keyboard
extracted: no receiver
call: Mouse
These lines demonstrate four different invocations of the same function:
showLabel("plain")is a plain call. Strict mode suppliesundefinedasthis.item.showLabel("method")is a method call. The receiver isitem.- Assigning
item.showLabeltoextractedcopies the function value, not a permanent connection toitem.extracted()is another plain call. call()invokes the function immediately and explicitly supplies the receiver as its first argument.
This is why “the object containing the function” is unreliable. At definition time, showLabel is not inside item at all, yet it works as an item method. Later, extraction removes the receiver even though the function originally came from that property.
call(), apply(), and bind()
All three methods let code control a regular function's receiver, but they do different jobs:
"use strict";
function format(quantity, suffix) {
return `${this.name}: ${quantity}${suffix}`;
}
const product = { name: "Cable" };
console.log(format.call(product, 2, " units"));
console.log(format.apply(product, [3, " units"]));
const formatProduct = format.bind(product, 4);
console.log(formatProduct(" units"));
The output is:
Cable: 2 units
Cable: 3 units
Cable: 4 units
call(receiver, arg1, arg2)invokes the function with separately listed arguments.apply(receiver, args)invokes it with arguments taken from an array-like value.bind(receiver, arg1)does not invoke the original function. It returns a new bound function and can also pre-fill arguments.
Calling call() or apply() on an arrow still passes normal arguments, but their receiver argument cannot change the arrow's lexical this.
Output prediction 2: an arrow inside a method
Arrows are especially useful when a callback should continue using the receiver of an enclosing method:
"use strict";
const team = {
name: "Coral",
members: ["Amin", "Lina"],
printMembers() {
this.members.forEach((member) => {
console.log(`${this.name}: ${member}`);
});
},
};
team.printMembers();
The output is:
Coral: Amin
Coral: Lina
printMembers is a regular method, so team.printMembers() gives it team as this. The arrow callback is created while that method is running and captures the method's this. forEach() does not need to supply the team as the callback receiver.
Replacing the arrow with a regular strict-mode callback changes the result:
"use strict";
const team = {
name: "Coral",
members: ["Amin"],
printMembers() {
this.members.forEach(function (member) {
console.log(this, member);
});
},
};
team.printMembers();
// undefined Amin
The regular callback has its own this. Array.prototype.forEach() calls it with undefined unless a thisArg is supplied. In strict mode that value remains undefined.
This behaviour belongs to forEach()'s callback contract; “callbacks always lose this” would be another misleading shortcut. An API decides how it invokes a callback. Some APIs supply a receiver, some accept an optional thisArg, and others perform a plain call.
Why an arrow is usually the wrong object method
An object literal does not create a new this scope. Therefore, an arrow written as an object property does not capture the object:
function createProduct(name) {
return {
name,
regularMethod() {
return this.name;
},
arrowMethod: () => this,
};
}
const product = createProduct.call({ scope: "outer" }, "Keyboard");
console.log(product.regularMethod()); // "Keyboard"
console.log(product.arrowMethod().scope); // "outer"
regularMethod() receives product from the call site. arrowMethod captures createProduct's this, which was explicitly set to the outer object. The braces of the returned object literal do not change that.
As a design judgement, use regular method syntax when the method should operate on whichever object invokes it. Use an arrow when retaining the surrounding receiver is the intended behaviour, commonly for a nested callback.
Strict mode and the global object
For a plain regular-function call, strict mode leaves this as undefined. In non-strict mode, JavaScript performs this substitution: undefined and null become globalThis, while primitive receivers are wrapped as objects.
Modern code often encounters strict behaviour automatically. ES modules are strict, and code inside class bodies is strict. Relying on a plain call to produce the global object is therefore fragile and can hide accidental global-state access.
Top-level this is a separate source of confusion because its value depends on the execution context:
- in a classic browser script, top-level
thisis normallyglobalThis; - in an ES module, top-level
thisisundefined; and - a Node.js CommonJS module wraps the file, so top-level
thisismodule.exportsrather than the global object.
That is also why examples involving a top-level arrow can appear to disagree across environments. The arrow is behaving consistently—it captures the surrounding this—but the surrounding environment is different.
Output prediction 3: constructor calls
A constructable regular function receives a newly created object when invoked with new:
"use strict";
function User(name) {
this.name = name;
}
const user = new User("Aish");
console.log(user.name); // "Aish"
console.log(user instanceof User); // true
The previous prototypes article explains the other half of this operation: the new object's internal [[Prototype]] is connected to User.prototype. The constructor's this and that prototype link are related parts of new, but they are not the same mechanism.
An arrow function cannot be used this way:
const User = (name) => {
this.name = name;
};
new User("Aish");
// TypeError: User is not a constructor
Arrow functions do not have the internal constructor capability required by new, and they do not have their own dynamic this. Regular function declarations and expressions are commonly constructable, although not every non-arrow function form is a constructor—for example, concise object methods cannot be invoked with new either.
Extracted methods used as callbacks
The extracted-method problem appears frequently in callback-heavy code:
"use strict";
const formatter = {
prefix: "MVR",
format(value) {
return `${this.prefix} ${value}`;
},
};
const prices = [25, 50];
// formatter.format is passed as a function value.
// map does not call it as formatter.format(value).
Two explicit ways to preserve the intended receiver are:
const withArrow = prices.map((price) => formatter.format(price));
const withBind = prices.map(formatter.format.bind(formatter));
console.log(withArrow); // ["MVR 25", "MVR 50"]
console.log(withBind); // ["MVR 25", "MVR 50"]
The arrow wrapper evaluates a real method call each time. The bound function stores the receiver in advance. Neither is universally preferable: an arrow is often clearest at one call site, while a bound function can be useful when the same callback is retained or reused.
A practical decision rule
When this appears inside a function, I now reason in this order:
- Is it an arrow? If yes, move outward to find the surrounding
this. - Is it a bound function? If yes, use the receiver established by its first binding, except for constructor-specific behaviour.
- Is it called with
new? If yes,thisis the new instance during construction. - Is it called with
call()orapply()? If yes, inspect the explicit receiver. - Is it called as
receiver.method()? If yes, the receiver isthis. - Otherwise it is a plain call:
undefinedin strict mode, with non-strict substitution rules when applicable.
This call-site approach also explains inherited prototype methods. A method may be found on User.prototype, but user.method() still receives user as this; the prototype object is where the function was found, not necessarily the receiver used to call it.
The same reasoning becomes more important in asynchronous JavaScript. Promise handlers, timers, event listeners, and queue callbacks are functions handed to another system for later invocation. The later event-loop article will focus on when those callbacks run; the call-site model here explains what receiver they see when they do.
All demonstrated outputs in this article were verified with Node.js v22.23.1. The browser-specific notes describe documented host behaviour and are not assumptions drawn from those Node.js runs.
Key takeaways
- A regular function's
thisis generally determined by how the function is called, not where it was defined. object.method()suppliesobjectas the receiver, even when the method was inherited from a prototype.- Extracting a method copies the function value but does not preserve its receiver.
call()andapply()invoke immediately with an explicit receiver;bind()returns a new function with a stored receiver.- An arrow does not create its own
this; it capturesthisfrom the surrounding lexical scope. - Use a regular object method when a dynamic receiver is required. An arrow is often useful inside that method when a nested callback should retain the outer receiver.
- Plain calls produce
undefinedin strict mode. Non-strict substitution and top-level host behaviour should not be treated as universal defaults. - Arrow functions cannot be constructors.