Why do `call`, `apply` and `bind` have no effect on an arrow function's `this`, and what happens to the optional `thisArg` argument of `Array.prototype.map` when the callback is an arrow?
answer
- nothing to overwrite, so nothing changes
- thisArg is discarded, arguments are not
- bind still partially applies
- array thisArg parameter is legacy surface
- caller-chooses-this requires an ordinary function
basics
~20 sAn arrow has no this binding to overwrite — it reads this from the enclosing scope. So call, apply, bind and the thisArg of methods like map still pass a value, but the arrow never consults it. Arguments are still forwarded normally.
solid answer
~50 s`call`, `apply` and `bind` work by supplying a value that the callee's own `this` binding will take. An arrow function creates no such binding: `this` inside it is a free variable resolved up the scope chain at definition, so there is nothing for the supplied value to land on. The calls do not throw and are not ignored wholesale — arguments are forwarded, `bind` still returns a new function and still partially applies leading arguments — only the thisArg is inert. The same is true of the second parameter of `Array.prototype.map`, `forEach`, `filter`, `some`, `every`, `find` and `flatMap`: passing a `thisArg` alongside an arrow callback is a no-op that reads as intentional in review, which is the real hazard. If a caller must be able to set `this`, the function has to be an ordinary function.
code
javascript · 15 linesconst obj = {
tag: 'obj',
run() {
const arrow = () => this.tag;
return {
plain: arrow(),
called: arrow.call({ tag: 'other' }),
bound: arrow.bind({ tag: 'bound' })(),
mapped: ['x'].map(() => this.tag, { tag: 'ignored' })
};
}
};
console.log(obj.run());
// { plain: 'obj', called: 'obj', bound: 'obj', mapped: [ 'obj' ] }go deeper
Remember that no caller can change an arrow's this — call, apply, bind and a thisArg parameter all leave it pointing at the scope where the arrow was written.
Explain the mechanism: those APIs supply a value for the callee's own this binding, and an arrow never creates one, so the value is discarded while arguments and partial application still work normally.
Spot the migration hazard — a leftover thisArg or an existing helper.call(obj) site that keeps running unchanged after a function becomes an arrow — and say how you would find those call sites.
Own the contract distinction: any function whose API promises the caller may choose the receiver must stay an ordinary function, and arrows are the deliberate choice where the this must be sealed against callers you do not control.
## What `call`, `apply` and `bind` actually do All three set the `this` value for a call. `Function.prototype.call(thisArg, ...args)` and `apply(thisArg, argsArray)` invoke immediately; `bind(thisArg, ...partial)` returns a new *bound function* exotic object that remembers the target, the bound `this`, and any leading arguments. Every one of them works by handing a value to the callee's function environment record, which the callee installs as its `this` binding. That step is the whole mechanism. ## Why an arrow ignores it An arrow function's environment record never creates a `this` binding. In spec terms its `[[ThisMode]]` is `lexical`, so evaluating `this` inside the body is an ordinary scope-chain lookup that walks outward to the nearest enclosing ordinary function, method, class field initializer, or the top level. The value handed in by `call` has nowhere to go, so it is discarded. ```javascript const obj = { tag: 'obj', run() { const arrow = () => this.tag; return [arrow(), arrow.call({ tag: 'other' }), arrow.bind({ tag: 'bound' })()]; } }; obj.run(); // ['obj', 'obj', 'obj'] ``` Crucially this is not an error and not a silent failure of the whole call — it is a partial no-op. That matters because the code still *looks* like it is doing something. ## What still works - Arguments are forwarded: `arrow.call(null, 1, 2)` passes `1` and `2` to the arrow. - `bind` still returns a new function object, with a `name` of `'bound '` plus the original name, and still partially applies leading arguments: `const add1 = ((a, b) => a + b).bind(null, 1); add1(2) === 3`. - The bound function is a distinct object, so identity-based bookkeeping still sees a new value each time you bind. So "bind does nothing to an arrow" is too strong; "bind cannot change an arrow's `this`" is precise. ## The `thisArg` parameter on array methods Several `Array.prototype` methods accept an optional second argument used as `this` inside the callback: `map`, `forEach`, `filter`, `some`, `every`, `find`, `findIndex`, `findLast`, `findLastIndex` and `flatMap`. Internally they invoke the callback with that value as the receiver — the same mechanism as `call`. An arrow callback therefore ignores it: ```javascript const ctx = { factor: 10 }; [1, 2].map(function (n) { return n * this.factor; }, ctx); // [10, 20] [1, 2].map((n) => n * this.factor, ctx); // ignores ctx ``` The arrow version reads `this` from wherever the arrow was written — `undefined` at module top level, which throws a TypeError, or some unrelated object elsewhere. The `thisArg` parameter is legacy surface from before arrows existed; with arrows in the language it is essentially dead, because the arrow already closes over the context you wanted. ## The review hazard The dangerous shape is a codebase in the middle of a `function` → arrow migration. A callback is converted to an arrow, the trailing `thisArg` argument stays behind, and the code still parses, still runs, and quietly means something else. The same happens when a helper documented as "call it with the widget as `this`" is reimplemented as an arrow: every existing `helper.call(widget)` call site keeps compiling and starts reading the wrong `this`. ## The design rule this implies If a function's contract includes "the caller chooses my `this`" — object methods, prototype methods, functions meant to be borrowed via `call`, callbacks written against a `thisArg` API, constructors — it must be an ordinary function. Arrows are for the opposite contract: "my `this` is already decided and no caller may change it". That immutability is a feature when you are handing a callback to code you do not control, and a defect anywhere the receiver is supposed to vary. One more consequence worth naming: because the capture is permanent, an arrow held by long-lived code keeps its captured scope alive. That is ordinary closure behaviour, but it means an arrow field or handler that captured a large object cannot be re-pointed later — you replace the function, not its `this`.
- Does `bind` do anything at all when the target is an arrow function?Yes — it returns a new bound function object and still partially applies any leading arguments, so `((a, b) => a + b).bind(null, 1)(2)` is `3`. Only the thisArg is inert, because the arrow has no `this` binding to install it into. Saying "bind does nothing" overstates it; saying "bind cannot retarget an arrow's `this`" is exact.
- When is an arrow's immunity to `call` actually desirable?When you hand a callback to code you do not control. A timer, an event dispatcher, or a third-party library may invoke your function with a receiver of its own choosing; an arrow guarantees your `this` stays whatever the surrounding code meant. The same immunity is a defect for anything designed to be borrowed or overridden.
- Why does the `thisArg` parameter exist on `Array.prototype.map` at all?It predates arrow functions. Before ES2015 the only ways to give a callback the right `this` were `thisArg`, a `bind` call, or a `var self = this` capture, so the built-ins offered it directly. With arrows closing over `this` for free the parameter is effectively dead surface, and passing it next to an arrow callback is a silent no-op.
saying these in an interview costs you the question
- Says arrow.call throws a TypeError
- Claims bind on an arrow does absolutely nothing
- Thinks the second bind wins over the lexical this
- Believes map's thisArg overrides an arrow callback
- Says arguments are dropped along with the thisArg