JavaScript · Syntax · Intermediate
Closures
Functions that remember their outer scope. Covers closure mechanics and practical patterns like counters and factories.
Quick topic start and explanations before exercises (exercises below):
Factory functions, the var-in-loop bug, and the module pattern
#Closure patterns: memoize, once, partial, and memory pitfalls
#Exercises:
Counter factory
#Write a function makeCounter() that returns a new counter object. Each call to makeCounter() should create an independent counter with increment() and getValue() methods.
function makeCounter() {
}
const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
console.log(counter.getValue());
Solution
function makeCounter() {
let count = 0;
return {
increment() { count++; },
getValue() { return count; },
};
}
const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
console.log(counter.getValue());
Multiplier factory
#Write a function makeMultiplier(factor) that returns a new function. That returned function takes a number and multiplies it by the original factor.
function makeMultiplier(factor) {
}
const triple = makeMultiplier(3);
const double = makeMultiplier(2);
console.log(triple(5));
console.log(double(7));
Solution
function makeMultiplier(factor) {
return function(n) {
return n * factor;
};
}
const triple = makeMultiplier(3);
const double = makeMultiplier(2);
console.log(triple(5));
console.log(double(7));
Once
#Write a function once(fn) that takes a function and returns a new function that can only be called once. On the first call it executes fn and returns the result; subsequent calls return undefined.
function once(fn) {
}
const init = once(() => {
console.log("initialized!");
return 42;
});
console.log(init());
console.log(init());
console.log(init());
Solution
function once(fn) {
let called = false;
return function(...args) {
if (!called) {
called = true;
return fn(...args);
}
};
}
const init = once(() => {
console.log("initialized!");
return 42;
});
console.log(init());
console.log(init());
console.log(init());
Adder factory
#Write a function makeAdder(x) that returns a function. The returned function takes a number y and returns x + y.
function makeAdder(x) {
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(3));
console.log(add10(3));
Solution
function makeAdder(x) {
return y => x + y;
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(3));
console.log(add10(3));
Memoize
#Write a function memoize(fn) that wraps a function and caches its results. If the function is called again with the same argument, return the cached result instead of calling fn again.
function memoize(fn) {
}
const slowSquare = memoize(n => {
console.log(`computing ${n}...`);
return n * n;
});
console.log(slowSquare(4));
console.log(slowSquare(4));
console.log(slowSquare(5));
Solution
function memoize(fn) {
const cache = {};
return function(arg) {
if (arg in cache) return cache[arg];
cache[arg] = fn(arg);
return cache[arg];
};
}
const slowSquare = memoize(n => {
console.log(`computing ${n}...`);
return n * n;
});
console.log(slowSquare(4));
console.log(slowSquare(4));
console.log(slowSquare(5));
Private variable
#Write a function makeBankAccount(initialBalance) that returns an object with deposit(amount) and withdraw(amount) methods and a getBalance() method. The balance should not be directly accessible from outside.
function makeBankAccount(initialBalance) {
}
const account = makeBankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(account.getBalance());
Solution
function makeBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) { balance += amount; },
withdraw(amount) { balance -= amount; },
getBalance() { return balance; },
};
}
const account = makeBankAccount(100);
account.deposit(50);
account.withdraw(30);
console.log(account.getBalance());
Delayed logger
#Write a function delayedLog(message, seconds) that logs the message to the console after the given number of seconds. Use setTimeout and a closure.
function delayedLog(message, seconds) {
}
delayedLog("hello", 1);
delayedLog("world", 2);
Solution
function delayedLog(message, seconds) {
setTimeout(() => {
console.log(message);
}, seconds * 1000);
}
delayedLog("hello", 1);
delayedLog("world", 2);
Clamp factory
#Write a function makeClamp(min, max) that returns a function. The returned function takes a number and clamps it between min and max (inclusive).
function makeClamp(min, max) {
}
const clamp0to100 = makeClamp(0, 100);
console.log(clamp0to100(-5));
console.log(clamp0to100(50));
console.log(clamp0to100(150));
Solution
function makeClamp(min, max) {
return n => Math.min(Math.max(n, min), max);
}
const clamp0to100 = makeClamp(0, 100);
console.log(clamp0to100(-5));
console.log(clamp0to100(50));
console.log(clamp0to100(150));
Call count wrapper
#Write a function withCallCount(fn) that wraps a function and returns an object with a call method and a getCount() method. call() invokes the original function and getCount() returns how many times it was called.
function withCallCount(fn) {
}
const wrapped = withCallCount(x => x * 2);
wrapped.call(5);
wrapped.call(10);
wrapped.call(3);
console.log(wrapped.getCount());
Solution
function withCallCount(fn) {
let count = 0;
return {
call(...args) {
count++;
return fn(...args);
},
getCount() { return count; },
};
}
const wrapped = withCallCount(x => x * 2);
wrapped.call(5);
wrapped.call(10);
wrapped.call(3);
console.log(wrapped.getCount());
Partial application
#Write a function partial(fn, ...preArgs) that takes a function and some arguments, and returns a new function that prepends those arguments to any future arguments when calling fn.
function partial(fn, ...preArgs) {
}
function add(a, b, c) {
return a + b + c;
}
const add10 = partial(add, 10);
console.log(add10(5, 3));
console.log(add10(1, 2));
Solution
function partial(fn, ...preArgs) {
return function(...laterArgs) {
return fn(...preArgs, ...laterArgs);
};
}
function add(a, b, c) {
return a + b + c;
}
const add10 = partial(add, 10);
console.log(add10(5, 3));
console.log(add10(1, 2));