Ways to write a function
Declaration, expression and arrow
Three styles. Declarations are hoisted (usable before they appear); arrows are short and do not have their own this.
console.log(double(4)); // works: declarations are hoisted
function double(n) { return n * 2; }
const triple = function (n) { return n * 3; };
const square = (n) => n * n;
const makePair = (a, b) => ({ a, b }); // wrap an object in ( )
console.log(triple(4), square(4), makePair(1, 2));
Output:
8
12 16 { a: 1, b: 2 }
Default, rest and spread
Defaults fill missing arguments; ...rest collects extras into an array; spread passes an array as arguments.
function greet(name = "friend", ...others) {
return `Hello ${name} and ${others.length} others`;
}
console.log(greet());
console.log(greet("Ram", "Sita", "Hari"));
const nums = [3, 9, 2];
console.log(Math.max(...nums));
Output:
Hello friend and 0 others
Hello Ram and 2 others
9
Named parameters with an object
Pass one object and destructure it: order does not matter and names make calls readable.
function createUser({ name, age = 18, admin = false } = {}) {
return { name, age, admin };
}
console.log(createUser({ name: "Sita", admin: true }));
Output:
{ name: 'Sita', age: 18, admin: true }
arguments and return values
A function returns undefined unless it returns something. Return an array or object to give back several values.
function minMax(arr) { return { min: Math.min(...arr), max: Math.max(...arr) }; }
const { min, max } = minMax([4, 9, 2]);
console.log(min, max);
console.log((function () {})());
Output:
2 9
undefined
Controlling this: call, apply, bind
call, apply, bind
Run a function with a chosen this. call takes arguments one by one, apply as an array, and bind returns a new function with this fixed.
function intro(greeting, punct) {
return `${greeting}, I am ${this.name}${punct}`;
}
const ram = { name: "Ram" };
console.log(intro.call(ram, "Hello", "!"));
console.log(intro.apply(ram, ["Namaste", "."]));
const ramIntro = intro.bind(ram, "Hi");
console.log(ramIntro("?"));
Output:
Hello, I am Ram!
Namaste, I am Ram.
Hi, I am Ram?
Partial application with bind
Fix some arguments in advance.
const multiply = (a, b) => a * b;
const double = multiply.bind(null, 2);
console.log(double(21));
Output:
42
Closures
A closure is a function that remembers the variables around it, even after the outer function has finished. It is how you keep private state.
A counter
count cannot be reached from outside, only through the returned functions.
function makeCounter() {
let count = 0;
return {
increment: () => ++count,
value: () => count,
};
}
const a = makeCounter(), b = makeCounter();
a.increment(); a.increment(); b.increment();
console.log(a.value(), b.value());
Output:
2 1
Function factories
A function that builds specialised functions.
const multiplier = (factor) => (n) => n * factor;
const triple = multiplier(3);
console.log(triple(5), multiplier(10)(5));
Output:
15 50
The classic loop trap
var shares one variable across all loop rounds; let makes a new one for each round.
const withVar = [], withLet = [];
for (var i = 0; i < 3; i++) withVar.push(() => i);
for (let j = 0; j < 3; j++) withLet.push(() => j);
console.log(withVar.map((f) => f()), withLet.map((f) => f()));
Output:
[ 3, 3, 3 ] [ 0, 1, 2 ]
Higher-order functions
Functions that take or return functions
once, compose and pipe are small building blocks.
const once = (fn) => { let done = false, r; return (...a) => (done ? r : ((done = true), (r = fn(...a)))); };
const init = once(() => { console.log("initialising"); return 42; });
console.log(init(), init());
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
console.log(pipe((x) => x + 1, (x) => x * 2)(5));
Output:
initialising
42 42
12
Memoization
Remember results so slow work is done once. A closure holds the cache.
const memo = (fn) => { const cache = new Map(); return (n) => cache.has(n) ? cache.get(n) : (cache.set(n, fn(n)), cache.get(n)); };
const fib = memo((n) => (n < 2 ? n : fib(n - 1) + fib(n - 2)));
console.log(fib(50));
Output:
12586269025
Recursion
A function that calls itself
Needs a base case to stop. Good for trees, nested data and divide-and-conquer.
const factorial = (n) => (n <= 1 ? 1 : n * factorial(n - 1));
console.log(factorial(5));
const sumNested = (a) => a.reduce((t, x) => t + (Array.isArray(x) ? sumNested(x) : x), 0);
console.log(sumNested([1, [2, [3, 4]], 5]));
Output:
120
15
Timers
setTimeout, setInterval, clearTimeout
Run code later or repeatedly. The callback runs after the current code finishes, even with a delay of 0.
console.log("first");
setTimeout(() => console.log("third (after 10 ms)"), 10);
setTimeout(() => console.log("second (0 ms, but later)"), 0);
console.log("end of script");
let n = 0;
const id = setInterval(() => { console.log("tick", ++n); if (n === 2) clearInterval(id); }, 20);
Output:
first
end of script
second (0 ms, but later)
third (after 10 ms)
tick 1
tick 2
Debounce
Wait until the calls stop for a while: used for search boxes and resize events (needs a browser to see in action).
function debounce(fn, ms) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), ms);
};
}
const search = debounce((q) => console.log("search for", q), 100);
search("p"); search("py"); search("pyt");
Output:
search for pyt
Try it yourself
- Write a function
makeAdder(n)that returns a function adding n to its argument. - Write
sum(...numbers)with a rest parameter. - Make a counter with
increment,decrementandreset.
Next lesson: dates, JSON and storage.

Comments
Loading comments…