Skip to content
academia.sh

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 this binding 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 arguments object; a variable number of arguments is taken with a rest parameter.
  • An arrow function’s this value 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 no prototype property.

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.

Start typing to search.

↑↓ Esc navigate · open · close