Lesson 15 / 21
Arrow Functions
The forms of the short syntax, the implicit return and object-return trap, the absence of the arguments object, and context coming from the enclosing scope.
Contents
Earlier lessons used spellings like (d) => d > threshold unexplained. This lesson
defines that syntax. The arrow function is a third function-definition form and offers
two gains: brevity and a change in context behavior.
The second is the real one. Brevity sometimes improves readability and sometimes reduces it; context behavior, on the other hand, is what makes an arrow function unusable in some places. By the end of this lesson, which form to choose where will be tied to a rule.
Syntax Forms
An arrow function consists of a parameter list and a body, separated by =>.
const square = (x) => x * x; const singleParam = x => x * 2; const two = (a, b) => a + b; const zero = () => 42; const withBody = (array) => { let total = 0; for (const value of array) total += value; return total; }; console.log(square(4), singleParam(4), two(1, 2), zero()); console.log(withBody([21.5, 19.75]));
16 8 3 42 41.25
Parentheses can be omitted for a single parameter; they are required for the no-parameter and multi-parameter forms. In this course, parentheses will always be written — a single form reduces the change needed when a parameter is added.
The body comes in two kinds. A body without curly braces is an expression, and its
value is implicitly returned; return is not written. A body with curly braces is a
block, and the return is written explicitly. Without return, the withBody
function’s result would have been undefined.
The Object-Return Trap
Returning an object with an implicit return creates a syntactic ambiguity: does the opening curly brace mean an object, or a block?
const broken = (name) => { station: name }; console.log(broken("A1"));
undefined
The parser read the curly braces as a block. The line station: name inside the block
is not an object property, it is a statement labeled station — the same labeled-loop
syntax as the Loops lesson. Because the block has no return, the result was
undefined. No error was thrown.
The fix is to wrap the object in parentheses:
const returnsObject = (name) => ({ station: name }); console.log(returnsObject("A1"));
{ station: 'A1' }
The parentheses signal that the curly braces are in an expression context; the parser now reads an object.
No Arguments Object
Arrow functions have no arguments object of their own:
const arrowFn = () => { try { return arguments.length; } catch (error) { return error.name + ": " + error.message; } }; console.log(arrowFn(1, 2));
ReferenceError: arguments is not defined
The message says “not defined” — an arrow function does not define such a name. A variable number of arguments is taken with a rest parameter:
const withRest = (...args) => args.length; console.log(withRest(1, 2));
2
The rule set in the Parameters lesson becomes mandatory here.
Context Comes From the Enclosing Scope
Arrow functions have no this binding of their own. The this in the body is the
this value where the function is defined. The lexical scope principle
established in the Naming and Scope Rules lesson also applies to binding in arrow
functions.
The rules of this binding are the next course’s subject. Here, only the observed
difference is covered, and it shows up most in callback functions:
const watcher = { threshold: 20, temperatures: [21.5, 19.75, 23], classicCallback() { try { return this.temperatures.filter(function (d) { return d > this.threshold; }); } catch (error) { return error.name + ": " + error.message; } }, arrowCallback() { return this.temperatures.filter((d) => d > this.threshold); }, }; console.log(watcher.classicCallback()); console.log(watcher.arrowCallback());
TypeError: Cannot read properties of undefined (reading 'threshold') [ 21.5, 23 ]
Both methods were called on the same object, and the outer this was the object
itself in both. The difference is in the inner function. The classic function
expression establishes its own this binding; in strict mode this binding is
undefined, and reading a property throws an error. The arrow function establishes no
binding of its own, uses the outer one, and the filter works as expected.
The same behavior also produces a result in the opposite direction. When an arrow
function is written as an object method, this does not bind to the object:
const station = { name: "A1", classicMethod() { return this === undefined ? "this: undefined" : `this.name: ${this.name}`; }, arrowMethod: () => { return this === undefined ? "this: undefined" : `this.name: ${this.name}`; }, }; console.log(station.classicMethod()); console.log(station.arrowMethod());
this.name: A1 this: undefined
The arrow function took the this value of where it was defined. Because this example
runs in a module file, the outermost this value is undefined — the module rule
shown in the Ways to Run Code lesson. The same line in script mode would give a
different value; in neither case would it be the object itself.
The rule follows from this: object methods are not written as arrow functions.
Cannot Be Used as a Constructor
Arrow functions cannot be called with new:
const Measurement = (name) => { this.name = name; }; try { new Measurement("A1"); } catch (error) { console.log(error.name + ": " + error.message); } function ClassicMeasurement(name) { this.name = name; } console.log(new ClassicMeasurement("A1").name); console.log(typeof Measurement.prototype, typeof ClassicMeasurement.prototype);
TypeError: Measurement is not a constructor A1 undefined object
The last line shows the reason: an arrow function has no prototype property. Why a
constructor call needs this property, and how the prototype chain is built, is the
next course’s subject.
Choosing a Form
The choice among the three definition forms comes down to these rules:
- Declaration: named helper functions in the outermost scope. Because it is hoisted, the file’s order is free.
- Arrow function: short callbacks passed to another function, and inner functions that need to use the enclosing context.
- Classic function expression: object methods and cases where
thisbinding should be determined by the call site.
An arrow function is not short everywhere. An operation whose body runs past a few lines and carries meaning on its own becomes both more readable and reusable when pulled out into a named declaration.
Applying It to the Measurement Script
Transformation chains are arrow functions’ best-fitting use:
const records = [ { station: "A1", temperature: 21.5 }, { station: "A2", temperature: NaN }, { station: "B1", temperature: 23 }, { station: "B2", temperature: 18.25 }, ]; const THRESHOLD = 20; const valid = (record) => !Number.isNaN(record.temperature); const aboveThreshold = (record) => record.temperature > THRESHOLD; const label = (record) => `${record.station}=${record.temperature}`; console.log(records.filter(valid).filter(aboveThreshold).map(label)); console.log( records .filter((r) => !Number.isNaN(r.temperature)) .map((r) => ({ ...r, deviation: r.temperature - THRESHOLD })), );
[ 'A1=21.5', 'B1=23' ]
[
{ station: 'A1', temperature: 21.5, deviation: 1.5 },
{ station: 'B1', temperature: 23, deviation: 3 },
{ station: 'B2', temperature: 18.25, deviation: -1.75 }
]
Both spellings belong to the same family. In the first, the filters were named and the chain stayed readable; in the second, short callbacks were written in place. In the second output, an object was returned, so the parentheses were required — the trap from this lesson’s second section.
The multi-line display in the second output is the runtime’s inspection format; an array is broken into lines once it exceeds a certain length. This format is not defined in the standard and varies across environments.
The object spread syntax ({ ...r }) was used here and will be defined in the Objects
lesson.
Summary
- In an arrow function, parentheses can be omitted for a single parameter; a body without curly braces is an expression and its value is implicitly returned.
- To return an object with an implicit return, the object is wrapped in parentheses;
without it, the curly braces are parsed as a block and the result is
undefined. - Arrow functions have no
argumentsobject; a variable number of arguments is taken with a rest parameter. - An arrow function’s
thisvalue comes from the scope it is defined in; this is the desired behavior in callbacks and the undesired behavior in object methods. - Arrow functions cannot be called with
new; they have noprototypeproperty.
Next Step
In this topic’s examples, error conditions were caught with try/catch, but the
structure was never defined. The next lesson covers error handling: the flow of
try/catch/finally blocks, the throw statement, built-in error types, and
defining your own error type. The measurement script’s response to broken data will
also be tied to a contract there.
To keep your progress and take notes, Log in
My notes
Log in to take notes.