JAVASCRIPT / FUNCTIONS
Function expressions against declarations
Tell function declarations and expressions apart, predict which names are callable before their definition line, and pick the right form for each job.
What you will learn
- Predict whether a function name is callable above the line that defines it
- Choose a declaration for a named helper, an expression when the function is a value
- Name a function expression for recursion and readable stack traces
- Read TypeError vs ReferenceError as a clue to which form was used
Understanding Function expressions against declarations
JavaScript reuses one keyword for two different grammatical roles. When `function` starts a statement, it is a declaration: the engine registers the name in the enclosing scope and gives it the finished function before the first line of that scope runs. When `function` appears where a value is expected, to the right of `=`, inside an argument list, or in an array or object literal, it is an expression that evaluates to a function object and does nothing else. If you do not store or immediately use that object, it is simply discarded.
The mental model worth keeping is two moments per scope: entry and execution. At entry the engine creates every binding the scope will need; a declaration's binding is already initialized with its function, a `var` binding holds `undefined`, and `let` and `const` bindings exist but are marked uninitialized. Execution then runs the lines in order, and the assignment on the expression's line is what finally puts the function into the variable. Both forms make the name exist early; what differs is whether the name already means something when your call runs.
A function expression may carry its own name, as in `const step = function tick() {...}`. That name is bound in a small scope wrapping only the body, so `tick` works for recursion inside and is invisible outside, while `step` is the name the rest of the code uses. An anonymous expression assigned to a variable inherits that variable's name for `.name` and for stack frames, but one passed straight into a call keeps an empty name. So: declarations for order-independent named helpers, expressions whenever the function is data being passed around or picked at runtime.
console.log(typeof square, typeof double);
console.log(square(4));
function square(n) {
return n * n;
}
var double = function (n) {
return n * 2;
};
console.log(double(4));
try {
triple(4);
} catch (err) {
console.log(err.name);
}
let triple = function (n) {
return n * 3;
};
console.log(triple(4));The same `function` keyword either binds a name at scope entry (declaration) or produces a value when execution reaches it (expression), and the position in the code decides which.
Worked examples
Named function expression
Shows that the name on a function expression lives only inside the function body.
const fact = function factorial(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
};
console.log(fact(5));
console.log(fact.name);
console.log(typeof factorial);Example explained
Line 1The right side of `=` is an expression, so the function object is built when this line executes and then stored in `fact`.
Line 2Inside the body, `factorial` resolves through a binding the engine creates just for that body, so the recursion keeps working even if `fact` is later reassigned.
Line 3`fact.name` is "factorial" because an explicit name on the expression wins over the variable's name.
Line 4`typeof factorial` is "undefined" out here because that inner binding never reaches the surrounding scope.
Where .name comes from
Compares the name given to an anonymous expression assigned to a variable with one passed directly as an argument.
function register(fn) {
console.log(JSON.stringify(fn.name));
}
register(function () {});
register(function named() {});
const inferred = function () {};
console.log(JSON.stringify(inferred.name));Example explained
Line 1`JSON.stringify` is used so an empty name shows up as "" instead of a blank line.
Line 2The first anonymous function has nothing to infer a name from, so `fn.name` is the empty string and stack frames show it as anonymous.
Line 3`function named() {}` in an argument position is still an expression: the name reaches `.name` but is never added to the calling scope.
Line 4`const inferred = function () {}` gets the name "inferred" because names are inferred from assignment targets, which is why assigning before passing gives better debug output.
Conditional definition
Shows why a function you define inside a block should be an expression assigned to an outer variable.
"use strict";
let greet;
if (true) {
function blockScoped() {
return "inner";
}
greet = function () {
return "assigned";
};
}
console.log(greet());
console.log(typeof blockScoped);Example explained
Line 1In strict code a function declaration inside a block belongs to that block, so `blockScoped` disappears once the block ends.
Line 2`greet` is declared outside the block, so the function expression assigned to it survives: the variable, not the function, decides visibility.
Line 3`typeof blockScoped` is "undefined" here, but sloppy non-strict scripts may leak it as a function under legacy web semantics, which is exactly why conditional definitions belong on the right side of an assignment.
Important notes
`typeof` does not shield you from a `let` or `const` binding: `typeof triple` above its declaration throws a ReferenceError, while `typeof` on a name declared nowhere at all quietly returns "undefined".
Position is decided by the parser, not by you: a statement that begins with the word `function` is read as a declaration, so an expression must sit where a value is expected, after `=`, in an argument list, or after an operator.
Common mistakes
Rewriting a helper as `const helper = function () {...}` but leaving calls above it: the call now throws a ReferenceError because the `const` binding is still in its temporal dead zone.
Calling a `var`-held function expression too early: instead of a clear ordering error you get "double is not a function", which sends people hunting for a typo in the name.
Calling the inner name of a named function expression from outside: `fact(5)` works but `factorial(5)` throws a ReferenceError, since that name exists only inside the body.
Try it yourself
Change, predict, then run
Paste, as one block, `console.log(area(2));` followed by `function area(r) { return Math.PI * r * r; }` and confirm it prints a number. Then change the definition to `const area = function (r) { return Math.PI * r * r; };`, rerun the same block, note the exact error, and fix it by moving the call below the definition.
Open the JavaScript workspaceCheck your understanding
Calling `f()` above `const f = function g() {};` throws a ReferenceError, while calling it above `function g() {}` works. What explains the difference?
- Function expressions are compiled lazily, so the body does not exist yet when the early call runs.
- `const` bindings are read-only, and reading one before its assignment is forbidden.
- The `const` binding exists as soon as the scope is entered but stays uninitialized until the assignment runs, while a declaration's binding is initialized with the function at scope entry.
- The name `g` lives only on the function object, so no binding is created in the surrounding scope at all.
Show answer
Both names are registered when the scope is entered, which is why the `const` version shadows any outer `f`; the difference is timing of initialization, and the `const` binding only receives the function when its assignment executes. Read-only-ness is not the cause, since `let` behaves identically before its assignment, and lazy compilation is an unrelated optimization that does not affect when a name becomes usable.