Arrow Functions Deep Dive
Lexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- javascript
- arrow-functions
- this
- functions
Arrow functions are not “shorter function.” They change this, arguments, new, and prototype. Use them where lexical this is the point — callbacks inside methods, Promise chains, array transforms. Skip them where you need a dynamic this or a constructor.
Syntax you’ll actually write
const add = (a, b) => a + b;
const square = (x) => x * x;
const makeUser = (name) => ({ name, active: true }); // parens for object literal
const sum = (...nums) => nums.reduce((a, n) => a + n, 0);
// block body needs explicit return
const parse = (raw) => {
const data = JSON.parse(raw);
return data.value;
};
Implicit return only works with a concise body. Returning an object literal requires ({ ... }) so { is not parsed as a block.
Lexical this (the whole point)
const timer = {
label: 'poll',
start() {
this.id = setInterval(() => {
console.log(this.label); // this === timer
}, 1000);
},
stop() {
clearInterval(this.id);
},
};
A classic function callback would rebind this (often undefined in strict mode / timers). Arrows close over this from start.
const bad = {
n: 0,
// lexical this is the outer scope (module), not bad
inc: () => {
this.n += 1;
},
};
bad.inc(); // does not update bad.n
Rule of thumb: object methods and prototype methods → function or method syntax. Nested callbacks → often arrows.
No arguments, no new, no prototype
const f = () => {
console.log(typeof arguments); // ReferenceError in modules / strict free arrows
};
// use rest: (...args) => args
const Person = (name) => {
this.name = name;
};
// new Person('Ada') → TypeError: Person is not a constructor
console.log((() => {}).prototype); // undefined
Rest parameters replace arguments. If you need new, use a class or a classic function.
Returning objects and async
const load = async (id) => {
const res = await fetch(`/api/${id}`);
return res.json();
};
// concise async
const loadName = async (id) => (await load(id)).name;
Async arrows work; generators cannot be arrows (function* only).
call / apply / bind and this
const show = () => this;
const obj = { x: 1 };
show.call(obj); // still outer this — bind is ignored for this
You can still partially apply args with wrappers; you cannot rebind an arrow’s this.
When to prefer classic functions
| Need | Use |
|---|---|
Method with dynamic this |
obj.method() / class method |
| Constructor | class or function |
arguments object / generator |
classic / function* |
| Hoisted declaration | function name() {} |
Callback needing lexical this |
arrow |
class Button {
constructor(el) {
this.el = el;
// bind once, or use arrow field (per-instance function)
this.onClick = this.onClick.bind(this);
el.addEventListener('click', this.onClick);
}
onClick() {
console.log(this.el);
}
}
Interview answer
“Arrows use lexical this, have no arguments object, and cannot be constructed. I use them for callbacks inside methods so this stays correct, and method syntax for anything that should be called as obj.fn(). Implicit object returns need parentheses.”
Related
Further reading
Related guides
- call, apply, and bindExplicit this control — call vs apply vs bind, partial application, bound constructors, and when arrows make bind pointless.
- Currying and Partial ApplicationCurry vs partial application — unary chains, bind, practical helpers, and when extra abstraction hurts readability.
- Default ParametersES6 default parameter values — evaluated at call time, temporal dead zone with lets, and why undefined triggers defaults but null does not.
- this Binding RulesHow JavaScript decides this — default, method, explicit call/apply/bind, new, and lexical this in arrows. With runnable examples.
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.