functions as variables
You may define functions much like any other variable.
const power = function(base, exponent) {
let result = 1;
for (let count = 0; count < exponent; count++) {
result *= base;
}
return result;
};
You can even define them with let instead of const in order to change the
definition later.
let launchMissiles = function() {
missileSystem.launch("now");
};
if safeMode {
launchMissiles = function() {}
};
function keyword
Another way to declare a function is with the function keyword, similar to
other languages. This method differs slightly from the others. It can be used
"before" it is defined in the file. In other words when functions are defined
with the function keyword they are conceptually moved to the top of their scope
and can be used by all the code in that scope. As a weird quirk this style
doesn't require the semi-colon at the end of the function declaration.
function square(x) {
return x * x;
}
arrow notation
The third and final way to declare a function is using "arrow notation". Arrow notation was added in 2015 and is essentially a shorthand style for the function keyword.
const power = (base, exponent) => {
let result = 1;
for (let count = 0; count < exponent; count ++) {
result *= base;
}
return result;
};
The arrow => comes after the list of parameters. If there's a single parameter
the parenthesis can be omitted and if the function's body contains a single
expression you can remove the braces and it will be automatically returned.
These two functions are the same:
const square1 = (x) => { return x * x; };
const square2 = x => x * x;
For functions without parameters use an empty set of parenthesis ().
higher order functions
Functions which take other functions as parameters. In the labels example arrow notation is used to create a nameless function as the repeat action.
function repeat(n, action) {
for (let i = 0; i < n; i++) {
action(i);
}
}
repeat(3, console.log);
let labels = [];
repeat(5, i => {
labels.push(`Unit ${i + 1}`);
});
console.log(labels);
optional arguments
If you pass too many arguments to a function in javascript the extra ones are discarded. If you pass too few they are undefined. This allows for lots of common mistakes, but also makes it easy to have optional arguments.
function minus(a, b) {
if (b === undefined) return -a;
else return a - b;
}
console.log(minus(10))
console.log(minus(10,5))
You can also set default values which will be used instead of undefined:
function power(base, exponent = 2) {
let result = 1;
for (let count = 0; count < exponent; count++) {
result *= base;
}
return result;
}
rest parameters
You can also explicity accept any number of arguments in javascript. For example, Math.max computes the maximum of all the arguments it is given.
function max(...numbers) {
let result = -Infinity;
for (let number of numbers) {
if (number > result) result = number;
}
return result;
}
console.log(max(4, 1, 9, -2));
// 9
The three dots must be before the function's last parameter, but it can have non-rest parameters. A similar three dot notation can be used to call a function with an array of arguments. The array is "spread out" and doesn't need to be the last argument in this case.
let numbers = [5, 1, 7];
console.log(Math.max(2, ...numbers, 9));
closures
Closures allow referencing a specific instance of a local variable in an enclosing scope. We can use this feature to create functions that multiply by an arbitrary amount.
function multiplier(factor) {
return number => number * factor;
}
let twice = multiplier(2);
console.log(twice(5)); // -> 10
recursion
A function may call itself, as long as it doesn't do it so often that it overflows the stack. Recursion allows some functions to be written in a different style, which can at times be easier to understand.
function power(base, exponent) {
if (exponent == 0) {
return 1;
} else {
return base * power(base, exponent - 1);
}
}
console.log(power(2,3))
In practice this style is a bad idea in javascript. Calling a function is generally slower than a simple loop and as a result this implementation of power is about 3 times slower than the looping version -- despite being a bit easier to understand. In a few cases recursion is faster than looping despite the overhead, this tends to happen with branching algorithms.
Consider this puzzle: by starting from the number 1 and repeatedly either adding 5 or multiplying by 3, an infinite set of numbers can be produced. How would you write a function, that given a number, tries to find a sequence of such additions and multiplications that produces that number?
For example, the number 13 could be reached by first multiplying by 3, then adding 5 twice, whereas the number 15 cannot be reached at all.
function findSolution(target) {
function find(current, history) {
if (current == target) {
return history;
} else if (current > target) {
return null;
} else {
return find(current + 5, `(${history} + 5)`) ||
find(current * 3, `(${history} * 3)`);
}
}
return find(1, "1");
}
console.log(findSolution(24));