JavaScript is one of the most popular programming languages in the world. It can do a lot and have some features that are ahead of many other languages.
In this article, we’ll look at ways to make a function call itself and scopes of functions.
Recursion
A recursive function is a function that calls itself.
We can use it do divide a problem into a set of subproblems, each with a trivial solution.
For instance, we can create a recursive function as follows:
const count = (i) => {
if (i === 10) {
return;
}
console.log(i);
count(i + 1);
}
count(0);
In the code above, we have a simple recursive function that stops with i
reaches 10.
For each value of i
, we log the value of i
to the console.
We should make sure that we have a base case to stop the function.
If a function returns the result of running itself recursively, JavaScript doesn’t optimize that by replacing it with a loop.
For instance, if we have:
const factorial = (i, a = 1) => {
if (i < 2) {
return a;
}
return factorial(i - 1, a * i)
}
const result = factorial(10);
Then JavaScript will turn that into a loop, so the call stack would be full if we call it too many times.
Scope
Scope in a programming language controls the visibility of variables and parameters.
In JavaScript, we have a function and block-scoped variables.
let
and const
are block-scoped and var
is function-scoped.
Therefore, to reduce confusion, we should use let
and const
to declare data.
For instance, if we have:
if (condition) {
let x = 1;
const y = 2;
//...
}
x
and y
are block-scoped, so they’re only available within the if
blocks.
We should just forget about var
and use let
or const
exclusively.
Variables should be declared as late as possible so that their lifetimes are short.
This reduces the need to follow the use of variables through many lines of code when we’re reading code.
Closure
Inner functions get access to parameters and variables of functions that are defined in.
We can use this to make values private and use them in inner functions.
For instance, we can write:
const obj = (() => {
let value = 0;
return {
increment(val) {
value += val;
},
getValue() {
return value;
}
};
})();
value
is private, so that we can update value
by incrementing it with val
.
So we update value
safely without exposing them to the outside.
We used an IIFE above, we can return an object that uses private variables.
Also, we can turn that into a factory function by removing the parentheses.
For instance, we can write:
const createName = (name) => {
return {
getName() {
return name;
}
};
};
const name = createName('foo').getName();
We return an object that returns the value of the name
parameter.
It’s not a constructor so that we can’t use it with the new
operator.
Inner functions have access to the variable itself rather than the value when it’s made.
For instance, if we have:
const addHandlers = (nodes) => {
for (var i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = () => {
alert(i);
}
}
}
addHandlers(document.querySelectorAll('button'))
If we run the code above, the alert box always shows 3 no matter which button we click.
This is because the loop body is found to the variable i
rather than the variable i
at the time the function is made.
To fix this, we can use let
instead of var
:
const addHandlers = (nodes) => {
for (let i = 0; i < nodes.length; i += 1) {
nodes[i].onclick = () => {
alert(i);
}
}
}
addHandlers(document.querySelectorAll('button'))
let
is block-scoped so that the onclick
handler will get the current value of i
rather than the value of i
when the loop is done.
Conclusion
Recursive functions are functions that call themselves directly or indirectly.
It’s useful for dividing problems into trivial subproblems.
Scopes of variables depends on how we declare them. If we declare them with let
or const
, then they’re block-scoped.
If we declare them with var
, then they’re function-scoped.
Closures allows us to access private variables.