You’ve recently written code like this:
const evens = (numbers) => {
return numbers.filter((n) => n % 2 === 0);
}
The expression (n) => n % 2 === 0
is an anonymous function defined within the body of the function evens
.
That could also be written like this, giving the nested function a name.
const evens = (numbers) => {
const isEven = (n) => n % 2 === 0;
return numbers.filter(isEven);
};
isEven
is a local variable in the function evens
; it only exists within the scope of the body of evens
.
In this case it might make more sense to move isEven
outside of evens
.
const isEven = (n) => n % 2 === 0;
const evens = (numbers) => {
return numbers.filter(isEven);
};
Since isEven
’s behavior depends only on the argument passed in to it, this works fine.
But suppose we wanted to write a function that returns the elements of numbers
that divisible by a specified number.
const divisibleBy = (numbers, divisor) => {
const hasDivisor = (n) => ... // something here
return numbers.filter(hasDivisor);
};
In this case, we need a function to pass to filter
that a) only takes one argument and b) can also refer to the variable divisor
.
const divisibleBy = (numbers, divisor) => {
const hasDivisor = (n) => n % divisor === 0;
return numbers.filter(hasDivisor);
};
Each time we call divisibleBy
a new hasDivisor
function is defined that checks its argument n
against the particular divisor passed to divisibleBy
.
const divisibleBy = (numbers, divisor) => {
return numbers.filter((n) => n % divisor === 0);
};
This does exactly the same thing without giving the function a name.
It “closes over” a variable that is defined in the environment where the function is defined.
hasDivisor
?We can’t move hasDivisor
out of divisibleBy
the way we did with isEven
because it needs to reference divisor
which only exists within divisibleBy
.
Define a higher-order-function that returns a function:
const hasDivisor = (divisor) => {
return (n) => n % divisor === 0;
}
Each time we call hasDivisor
it creates a new closure that closes over the variable naming the specific value we passed in.
… that we can return that function since the variable divisor
only exists within hasDivisor
and we’re returning from that function?
Turns out, it’s totally cool to do this. And quite handy.
const isEven = hasDivisor(2);
const isRoundNumber = hasDivisor(10);
divisibleBy
const divisibleBy = (numbers, divisor) => {
return numbers.filter(hasDivisor(divisor));
};
const counter = (start) => {
return () => start++;
}
const c1 = counter(100);
const c2 = counter(100);
c1() ⟹ 100
c2() ⟹ 101 // The value of the variable has changed
c2() ⟹ 100 // Each function has its own closed-over variable
The closures created by counter
don’t just capture the initial value of start
; they actually keep the variable alive.
const complement = (f) => {
return (x) => !f(x);
}
What must f
be?
What kind of value is (x) => !f(x)
?
const isEven = (n) => n % 2 === 0;
const isOdd = complement(isEven);