In JavaScript, a function is a value, just like a number or a string. That means you can store one in a variable, pass one into another function, or return one from a function. A function expression is a function written in a place where a value is expected, most often stored in a variable.
// Function DECLARATION: a statement on its own
function multiply(a, b) {
return a * b;
}
// Function EXPRESSION: a function used as a value, stored in a variable
const times = function(a, b) {
return a * b;
};
console.log(multiply(4, 3), times(4, 3));
Both are called the same way, and both work the same when called. Once a function expression is stored in a variable, the variable name is how you call it.
| Declaration | Expression | |
|---|---|---|
Name after function | Required | Optional (usually left out) |
| Ends with a semicolon | No | Yes (it is an assignment statement) |
| Callable before its line | Yes (hoisted) | No |
The expression above has no name after the word function. It is an anonymous function: a function without a name. Functions stored in variables do not need names, because the variable provides one.
Function expressions can have a name, though. This is called a named function expression:
const add = function add(a, b) { return a + b; };
The inner name shows up in error messages, which helps debugging, and lets the function call itself:
const countdown = function tick(n) {
if (n < 0) return;
console.log(n);
tick(n - 1); // calls itself with a smaller number
};
countdown(2);
A function that calls itself is called recursive. The if (n < 0) return; line is essential: it is what makes the calls stop, just like a loop's condition.
A function expression is part of an assignment statement (const x = ...;), so it ends with a semicolon after the closing brace, just like const n = 5; does. A declaration does not need one.
Declarations can be called before the line where they appear. Expressions cannot, because the variable does not hold the function until that line runs:
console.log(early()); // works
function early() { return "works"; }
console.log(late()); // fails
const late = function() { return "works"; };
That is the temporal dead zone from JS Let: late is a const, and it cannot be touched before its line.
const precisely because of this: the code reads top to bottom, and a function cannot be used before it is defined. And as a bonus, const means the function cannot accidentally be replaced later.
Because a function is a value, you can copy it to another variable:
const shout = function(text) {
return text.toUpperCase() + "!";
};
const yell = shout; // no parentheses: copies the function itself
console.log(yell("hey"));
A function can be passed as an argument to another function. The receiving function decides when to call it:
function run(fn) {
return fn();
}
const sayHello = function() {
return "Hello";
};
console.log(run(sayHello)); // pass the function, no ()
A function passed in like this is called a callback. You can also write the function right inside the call:
function repeat(times, action) {
for (let i = 1; i <= times; i++) {
action(i);
}
}
repeat(3, function(n) {
console.log("Lap " + n);
});
You have already seen this pattern with addEventListener("click", handleClick). Callbacks are everywhere in JavaScript: events, timers (JS Timers), and array methods all use them.
A function can build and return a brand new function. The returned function remembers the values that were around when it was made:
function makeMultiplier(factor) {
return function(n) {
return n * factor;
};
}
const double = makeMultiplier(2);
const triple = makeMultiplier(3);
console.log(double(10), triple(10));
double still knows factor is 2, even though makeMultiplier finished running long ago. Closures are an advanced topic; for now, just notice that it works.
Wrap a function expression in parentheses and add () to run it the moment it is defined. This is an Immediately Invoked Function Expression (IIFE), which you met in Function Invocation:
(function() {
const secret = "hidden";
console.log("Setup done");
})();
// secret does not exist out here
You will see IIFEs in older code as a way to keep variables private, so they do not clash with other scripts. The theme script on these lesson pages uses one.
Next up, arrow functions give you a much shorter way to write function expressions, and they are what most modern code uses for callbacks.
sayHello is the function; sayHello() calls itrun(sayHello()) runs sayHello first and passes its result ("Hello"), and then run fails trying to call a stringWhich kind of function can be called before the line where it is defined?
constletWhat does this log?
function twice(fn, x) {
return fn(fn(x));
}
const addThree = function(n) { return n + 3; };
console.log(twice(addThree, 10));
16. The inner call gives addThree(10) = 13, then the outer call gives addThree(13) = 16.What does const g = makeMultiplier(5); console.log(g(4)); log, using the makeMultiplier function above?
20. makeMultiplier(5) returns a function that remembers factor is 5. Calling it with 4 gives 4 × 5.double as a const function expression and check it still worksapplyTwice(fn, x) that returns fn(fn(x)). Test it with a doubling functionmakeGreeter(greeting) that returns a function taking a name, then make a "Hi" greeter and a "Welcome" greeter