First Class Functions
In JavaScript, functions are values. You can store them in variables, pass them to other functions, and return them from functions.
First class functions means functions can be assigned to variables, stored in arrays, passed as arguments (callbacks), and returned from other functions.
EXERCISE
1Functions Are Values
In JavaScript, functions are not special commands. They are values, just like numbers and strings. You can store them in variables, put them in arrays, and pass them around.
A function in is a value. Not a special construct that happens to be callable, but a value of the same standing as a number or a string: it can be stored in a variable, put in an array, passed to another function and returned from one. The phrase for this is that functions are first class, and almost everything in the rest of this unit is a consequence of it.
Assigning a function to another name copies the value, not the result:
function sayHi() {
console.log("Hi!");
}
const greeting = sayHi;
greeting(); // "Hi!"
sayHi(); // "Hi!"
The absence of parentheses is the entire point. sayHi is the function; sayHi() is the result of running it. Writing const greeting = sayHi() would have called it immediately and stored undefined, which is one of the most common mistakes when passing callbacks.
Functions in an array:
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
const operations = [add, subtract];
console.log(operations[0](10, 5)); // 15
console.log(operations[1](10, 5)); // 5
operations[0] evaluates to the function, and the second pair of parentheses calls it. Nothing special is happening; indexing an array of functions works exactly like indexing an array of numbers.
Functions as object properties:
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
};
console.log(calculator.add(2, 3)); // 5
A function stored on an object is usually called a method, but that word describes where it lives rather than what it is. It is still a value on a property.
This is also why typeof answers the way it does:
console.log(typeof add); // "function"
console.log(typeof add(1, 2)); // "number"
The first is the value itself. The second is what that value produces when you run it. Keeping those two apart is most of what it takes to read the rest of this unit.
Because a function is a value, it also carries properties like one:
function greet(name, greeting) {
return greeting + ", " + name;
}
console.log(greet.name); // "greet"
console.log(greet.length); // 2
name is the function's own name and length is how many parameters it declares. Neither is often needed directly, but they are a reminder of what is actually going on: a function is an object that can be called, and the calling is one capability among several.
EXERCISE
2Passing Functions as Arguments
Since functions are values, you can pass one function into another function, just like you would pass a number or string. The function that gets passed in is called a callback.
Because a function is a value, it can be passed to another function as an argument. A function given to another function to be called later is a callback, and you have been using them since your first addEventListener or setTimeout.
function twice(fn, value) {
return fn(fn(value));
}
const increment = (n) => n + 1;
console.log(twice(increment, 5)); // 7
twice knows nothing about incrementing. It knows how to apply whatever function it was handed, twice. Pass it a different function and it does something entirely different, without any change to twice itself:
const double = (n) => n * 2;
console.log(twice(double, 5)); // 20
That is the real payoff. The behaviour that varies has been lifted out of the function and made an input.
The parentheses mistake is worth showing directly, because the error appears later:
setTimeout(increment, 1000); // correct: hand over the function
setTimeout(increment(5), 1000); // wrong: calls it now, hands over 6
The second line runs increment immediately and passes its result to setTimeout, which then has a number where it expected a function. Nothing throws at that line. A second later, the timer tries to call 6 and fails, far away from the mistake.
Anonymous functions are common here because the function is often needed only once:
const numbers = [1, 2, 3];
numbers.forEach(function (n) {
console.log(n);
});
numbers.forEach((n) => console.log(n));
Both pass a function that has no name and no existence outside the call. Naming it would add a line and a name to keep track of for no benefit.
Pro Tip: When a callback does not run, check the parentheses first.
fnpasses the function,fn()passes its return value, and the difference is one character that changes what the receiving code is holding.
Passing a function also lets the receiving code decide when to run it, which is the whole basis of asynchronous work:
function retry(attempts, fn) {
for (let i = 1; i <= attempts; i++) {
const result = fn(i);
if (result) return "succeeded on attempt " + i;
}
return "gave up";
}
console.log(retry(3, (n) => n === 2)); // "succeeded on attempt 2"
retry owns the loop and the stopping condition. What counts as success is supplied by the caller and can differ at every call site, without retry knowing anything about it.
EXERCISE
3Returning Functions from Functions
A function can also return another function. This pattern is the gateway to closures, which you will learn in detail in Unit 5.
A function can also return a function. The returned function is an ordinary value like any other, and it carries with it the variables that were in scope where it was created.
function createMultiplier(factor) {
return function (n) {
return n * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
createMultiplier ran twice and produced two different functions. double remembers factor as 2; triple remembers it as 3. Neither can see the other's factor, and both continue to work long after createMultiplier returned.
That last part deserves , because it contradicts what unit 1 said about execution contexts being destroyed on return. The context is gone, but factor survived, because the returned function still refers to it. A function that keeps a reference to variables from where it was defined is a closure, and unit 5 is entirely about them.
This pattern is how you build a specialised function from a general one:
function prefixWith(prefix) {
return function (message) {
return prefix + ": " + message;
};
}
const error = prefixWith("ERROR");
const info = prefixWith("INFO");
console.log(error("disk full")); // "ERROR: disk full"
console.log(info("started")); // "INFO: started"
Written once, configured twice, and the configuration is fixed at the moment of creation rather than passed at every call.
The arrow form makes the shape clearer once you are used to reading it:
const prefixWith = (prefix) => (message) => prefix + ": " + message;
console.log(prefixWith("WARN")("low memory")); // "WARN: low memory"
Read it left to right: take a prefix, hand back a function that takes a message, and that function produces the string. The double call on the last line is just the two steps run back to back.
The two returned functions really are independent, which is easy to confirm:
const addTax = createMultiplier(1.2);
const halve = createMultiplier(0.5);
console.log(addTax(100)); // 120
console.log(halve(100)); // 50
console.log(addTax(100)); // 120, unchanged
Calling halve does not disturb addTax. Each call to createMultiplier produced a separate context with its own factor, and the returned function holds on to its own. This is the property that makes the pattern safe to use repeatedly rather than a shared piece of state waiting to be corrupted.