JAVASCRIPT / FUNCTIONS
Arrow functions and shorter call signatures
Write arrow functions in both body forms, know exactly when parentheses are required, and predict the `this` inside one from where it was written.
What you will learn
- Drop the braces and `return` when an arrow's body is a single expression
- Wrap a returned object literal in parentheses: `id => ({ id })`
- Omit parameter parentheses only for exactly one plain identifier parameter
- Work out an arrow's `this` from where it was written, not from how it is called
Understanding Arrow functions and shorter call signatures
An arrow function is written as a parameter list, `=>`, and a body, and it is always an expression: you get a function value to assign, pass, or return, never a declaration. The parameter list keeps its parentheses except in one case, exactly one parameter that is a plain identifier, so `n => n * 2` is legal while `() => 1`, `(a, b) => a + b`, `(a = 1) => a`, and `({ id }) => id` all need them. The body has two forms: a single expression, whose value is returned without writing `return`, or a braced block, which behaves like any other function body and returns `undefined` unless you write `return`.
Those two body forms explain almost every arrow bug. When the parser reaches `{` directly after `=>` it commits to a block of statements, so `id => { id }` evaluates `id`, discards it, and returns `undefined`, while `id => ({ id })` puts the brace in expression position and returns the object. Once you need a local `const`, a loop, or an early exit, take the block and the explicit `return`; the concise body is shorthand for one expression, not a smaller kind of function.
The other difference has nothing to do with length. An arrow has no `this`, `arguments`, `super`, or `new.target` of its own; those names resolve outward through the scope chain like any other free variable, and they are fixed when the arrow is created. That is why an arrow is the right callback inside a method, since `this.items.map(x => this.format(x))` works with no saved `self`, and the wrong choice for the method itself or for anything called with `new`: its `this` would be whatever surrounded the object literal, and arrows are not constructors.
const double = n => n * 2;
const pair = (a, b) => [a, b];
const toEntry = word => ({ word, length: word.length });
const add = a => b => a + b;
console.log(double(21));
console.log(pair('x', 'y'));
console.log(toEntry('arrow'));
console.log(add(2)(3));
console.log([1, 2, 3].map(double));An arrow function is an expression that can return a single expression implicitly and that takes `this` from the scope where it was written rather than from the call site.
Worked examples
Block body against expression body
Shows why a braced body returns undefined and how parentheses rescue a returned object literal.
const asBlock = (x, y) => { x, y };
const asObject = (x, y) => ({ x, y });
const clamp = n => {
const capped = Math.min(n, 10);
return Math.max(capped, 0);
};
console.log(asBlock(1, 2));
console.log(asObject(1, 2));
console.log(clamp(42), clamp(-7), clamp(4));Example explained
Line 1In `asBlock`, the `{` after `=>` starts a statement block, so `x, y` is an expression statement whose value is discarded and the call yields `undefined`.
Line 2In `asObject`, the wrapping parentheses force expression position, so `{ x, y }` is an object literal using shorthand properties.
Line 3`clamp` must use a block because it declares `capped`, and a block body only produces a value through an explicit `return`.
Lexical this cannot be reassigned
Compares an arrow and a plain function created in the same method, including what `call` can and cannot change.
const probe = {
tag: 'probe',
compare() {
const arrow = () => this.tag;
const plain = function () { return this && this.tag; };
return [arrow(), plain(), plain.call({ tag: 'other' }), arrow.call({ tag: 'other' })];
}
};
console.log(probe.compare());Example explained
Line 1`arrow()` reports `'probe'` because the arrow has no `this` of its own and reuses `compare`'s, which the `probe.compare()` call set to `probe`.
Line 2`plain()` is called with no receiver, so its own `this` binding is `undefined` (or the global object in a sloppy script) and the guard evaluates to `undefined`.
Line 3`plain.call({ tag: 'other' })` works because `call` sets the `this` binding of a function that has one.
Line 4`arrow.call({ tag: 'other' })` still returns `'probe'`: `call` passes arguments but has no binding to overwrite.
Shorter callback signatures
Uses named arrows and an arrow returning an arrow to keep callbacks readable at the call site.
const words = ['delta', 'a', 'beta', 'cc'];
const byLength = (a, b) => a.length - b.length;
const longerThan = min => word => word.length > min;
console.log(words.slice().sort(byLength));
console.log(words.filter(longerThan(2)));
console.log(words.map(w => w.length).reduce((sum, n) => sum + n, 0));Example explained
Line 1`byLength` is a two-parameter arrow, so the comparator reads as one expression instead of a nested `function` block with a `return`.
Line 2`longerThan` returns a second arrow that closes over `min`, so `longerThan(2)` hands `filter` a ready-made predicate.
Line 3`slice()` copies first because `sort` mutates in place; that is unrelated to the arrow, which only supplies the comparison.
Line 4In `(sum, n) => sum + n` the parentheses are mandatory, since only a single identifier parameter may appear bare.
Important notes
`call`, `apply`, and `bind` still pass arguments to an arrow but cannot change its `this`, and `new` applied to an arrow throws a TypeError because arrows have no `prototype` and are not constructors.
A missing pair of parentheses around a returned object fails in two different ways: `x => { a: 1 }` silently returns `undefined` because `a:` becomes a label, while `x => { a: 1, b: 2 }` is a SyntaxError.
Common mistakes
Writing `n => { n * 2 }` and getting `undefined` from every call: the braces open a statement block, so the multiplication happens and its value is thrown away.
Returning an object without parentheses, as in `id => { id: id }`: `id:` is parsed as a statement label, the function returns `undefined`, and the failure shows up much later as a property read on `undefined`.
Using an arrow as an object method, as in `{ tag: 'x', read: () => this.tag }`: `this` is whatever surrounded the object literal, so `read()` yields `undefined` or throws instead of returning `'x'`.
Try it yourself
Change, predict, then run
In a browser console define `const rows = [{ id: 1, qty: 2 }, { id: 2, qty: 5 }]`, then run `rows.map(({ qty }) => ({ total: qty * 3 }))` and note that the destructured parameter needs its own parentheses. Run it again with the parentheses around the object literal removed and confirm you get `[undefined, undefined]`.
Open the JavaScript workspaceCheck your understanding
A top-level `const getTag = () => this.tag;` is later attached to an object and called as `obj.getTag()`. What decides the value of `this` inside the arrow?
- `obj`, because calling a function as a property of an object sets `this` to that object
- The first argument passed at the call, which arrows treat as their receiver
- The `this` of the scope where the arrow was written, which no call site can change
- `globalThis`, because arrow functions always execute in the global context
Show answer
An arrow never receives its own `this` binding, so `this` is resolved lexically, like a closed-over variable, at the place the arrow was written. Option 0 states the normal rule for method calls, but that rule only assigns a `this` binding to functions that have one, so attaching the arrow to `obj` changes nothing; the same reasoning is why `call` and `bind` cannot help either.