Command Palette

Search for a command to run...

Hectal
PHASE 3Beginner ~26 min· topic 2 of 9

Topic 3.2

Declarations, Expressions and Arrow Functions

In one line

JavaScript has three main ways to write a function: a declaration (function add() {}), a function expression stored in a variable, and a short arrow function ((a, b) => a + b); they differ in hoisting, this, arguments and whether they can be used with new.

Think of it like this

Three ways to save a friend's phone number. You can write it in your address book under their name (a declaration: it's there from the moment you open the book). You can write it on a sticky note and stick the note on the fridge (an expression stored in a variable: it only exists once you've stuck it up). Or you can speed-dial it as a single button (an arrow function: shorter, quicker, but with fewer features). All three ring the same friend.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Function declaration
A statement that starts with function name(...). It exists before the rest of the code runs.
Function expression
A function created as a value, for example on the right side of =.
Anonymous function
A function written without a name of its own.
Arrow function
A short function written with =>, like x => x * 2. Added in ES2015.
Concise body
An arrow body that is one expression, with no braces. Its value is returned automatically.
Implicit return
Returning a value without writing return, which only concise arrow bodies do.
Lexical this
An arrow uses the this of the code around it instead of getting its own.
Constructor
A function called with new to build objects. Arrows can't be constructors.

Step by step

01The declaration

The form you learned in Topic 3.1. It's a statement on its own and it's hoisted, so the call can come first.

forms.jswhole filejavascript
console.log(add(2, 3)); // 5, works even above the definition

function add(a, b) {
  return a + b;
}

02The same thing as an expression

Now the function is a value stored in a const. The code is nearly identical, but you can only call it after this line has run. Note the ; at the end: it's an assignment statement.

forms.jswhole filejavascript
const subtract = function (a, b) {
  return a - b;
};
console.log(subtract(9, 4)); // 5

03Shrink it to an arrow

Remove function, add => after the parameters. Because the body is a single return, you can also remove the braces and the word return: the expression is returned implicitly.

forms.jswhole filejavascript
const multiply = (a, b) => {
  return a * b;
};

// same function, concise body
const multiply2 = (a, b) => a * b;

const square = x => x * x;   // one parameter: parentheses optional
const hello = () => "hi";     // no parameters: () needed

04Pick the form

The choice mostly depends on hoisting and this. This chart covers everyday cases.

Pick the formdiagram
Rendering diagram…

05Know what arrows leave out

Arrows have no own this, no arguments, no prototype, and can't be used with new. For callbacks those are features, not losses: the arrow sees the surrounding this and arguments, which is usually what you want.

Try it yourself

  1. 1

    Convert to arrows

    In the first example, rewrite add and subtract as arrow functions with concise bodies. Predict: does the first console.log line change? Does the second?

  2. 2

    Fix the object trap

    In the second example, fix doubleBlock in two different ways: add return, or remove the braces. Both should print 8.

  3. 3

    Swap the timer callbacks

    In the third example, change the arrow inside start into a function () { ... }. Predict what this.label prints now, then run it.

Code & diagrams

Three ways to write the same idea ES2015+ New tab

`factorial` is only visible inside its own body, so `typeof factorial` outside is `"undefined"`.

Console

(nothing was printed)

Expected console output

5 5 12 25 hi
add subtract multiply
120 factorial undefined
Implicit return and the object-literal trap ES2015+ New tab

In `makeUserWrong`, `{ name: name }` is read as a block containing a label `name:`, so nothing is returned. Parentheses make it an object.

Console

(nothing was printed)

Expected console output

8
undefined
undefined
{ name: 'Asha' }
[ 10, 20, 30 ]
Where arrows behave differently ES2015+ New tab

`this` gets a full topic in the objects phase. For now: arrows borrow it from where they are written.

Console

(nothing was printed)

Expected console output

regular got 3 arguments
Person { name: 'Asha' }
TypeError: arrow is not a constructor
arrow has prototype? false
arrow sees: Kitchen timer
regular sees: undefined

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Call a function expression before its line

Move console.log(subtract(9, 4)); above const subtract = function ....

terminal
$ Console
── what you'll see ──
Uncaught ReferenceError: Cannot access 'subtract' before initialization

Break #2

Use an arrow as an object method

Write const user = { name: "Asha", hi: () => "Hi " + this.name }; and call user.hi().

terminal
$ console.log(user.hi())
── what you'll see ──
Hi undefined

Myth vs fact

Myth

Arrow functions are just a shorter way to write function.

Fact

They also have no own this, no arguments, no prototype, and can't be used with new.

Myth

Arrow functions are faster.

Fact

Engines optimise both equally. Choose by behaviour and readability, not speed.

Myth

Anonymous functions have no name at all.

Fact

When assigned to a variable or property, the engine infers a name from it, which also shows in stack traces.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Name inference follows the spec's NamedEvaluation: it applies to const f = () => {}, obj = { f: function () {} }, default parameter values and class fields, but not to a function passed directly as an argument, which stays "" and appears as (anonymous) in stack traces.

  • ▸

    A named function expression's name is bound in a tiny scope of its own and is read-only inside the function: assigning to it fails silently in sloppy mode and throws TypeError in strict mode.

  • ▸

    A function declaration inside a block (if (x) { function f() {} }) behaves differently in sloppy and strict mode (Annex B of the spec). Use const f = () => {} inside blocks to avoid the confusion.

  • ▸

    Arrows are a natural fit for class fields (handleClick = () => { ... }) because the this they capture is the instance; the cost is one function per instance instead of one shared on the prototype.

Remember this

  1. 1

    A function declaration is a statement that starts with the word function and has a name: function add(a, b) { return a + b; }. Its special power is hoisting: the whole function is ready before any code in its scope runs, so you can call it on a line *above* where it's written (the full story is Topic 3.5 — Hoisting and the Temporal Dead Zone).

  2. 2

    A function expression creates a function as a value, in a place where a value is expected, usually on the right of =: const subtract = function (a, b) { return a - b; };. The function has no name of its own here (it's anonymous), but JavaScript infers one from the variable, so subtract.name is "subtract". You can also give it a name: const fact = function factorial(n) { ... };. That inner name, factorial, is visible only inside the function itself, which is handy for recursion and makes stack traces clearer. Expressions are not usable before their line runs, because the variable holding them isn't filled yet.

  3. 3

    An arrow function (since ES2015) is a shorter function expression: const multiply = (a, b) => a * b;. Drop the word function and put => (an "arrow") between the parameters and the body. With exactly one parameter you may drop the parentheses: x => x * x. With zero parameters you need empty ones: () => "hi".

  4. 4

    Arrows have two body styles. A concise body is a single expression after =>, and its value is returned automatically: this is the implicit return, n => n * 2 returns n * 2. A block body uses braces, n => { return n * 2; }, and then you must write return yourself, exactly as in a normal function. One trap: to implicitly return an object literal, wrap it in parentheses, name => ({ name: name }), because a { straight after => is read as the start of a block body.

  5. 5

    Arrows are not only shorter; they behave differently in four ways. (1) They have **no own this**: inside an arrow, this is whatever it was in the surrounding code (called **lexical this**), which makes them perfect for callbacks inside methods and wrong as object methods themselves (you'll meet this properly in the objects phase). (2) They have **no arguments object (use rest parameters, Topic 3.3). (3) They can't be called with new** (they're not constructors). (4) They have no prototype property. Declarations and function expressions have all of these.

  6. 6

    Which one to use? A common modern style: declarations for named, top-level building blocks of a program (they read well and are hoisted), arrows for short callbacks passed to other functions (list.map(x => x * 2), addEventListener("click", () => ...)), and function expressions when you need a function value that also needs its own this. Whatever you pick, be consistent inside a project.

Explain it without notes

01

What is the difference between a function declaration and a function expression?

02

What is an implicit return, and how do you implicitly return an object from an arrow function?

03

Name the behavioural differences between arrow functions and regular functions.

04

When would you choose an arrow function, and when a regular one?

Practice

01

Rewrite function isAdult(age) { return age >= 18; } as an arrow function with a concise body.

02

Write an arrow toPoint that takes x and y and returns an object { x, y } with an implicit return.

03

Use an arrow with map to turn [1, 2, 3] into ["#1", "#2", "#3"].

Trade-offs

  • ↔

    Arrows make callbacks short and keep this, but a long chain of anonymous arrows is harder to debug than named functions; name callbacks that are more than a line or two.

  • ↔

    Declarations can be called before they appear, which lets you put the main flow at the top of a file; some teams prefer expressions so code always reads top to bottom in run order.

Done when you can

  • I can write the same function as a declaration, an expression and an arrow.

  • I know when an arrow returns implicitly and when it needs return.

  • I can return an object literal from an arrow.

  • I can list how arrows differ from regular functions.

  • I can choose a form for a callback, a method and a top-level helper.