Closure is as Closure does
Stephen Firecrow Silvernight
Posted on April 1, 2024
I'd like to share my experience of when to use closures and when to use more basic things.
The old joke I've come to enjoy is this, "When do Senior and Junior engineers write the same thing... When the clever function is not necessary."
Let's consider to possible solutions to using data from an Array.
const items = [objA, objB, objC, objD];
function doStuff(obj){
// do stuff here
}
// Version One - with a closure
items.forEach((obj) => doStuff(obj));
// Version Two - with a for loop
for(let i = 0, len = items.length; i < len; i++){
doStuff(items[i]);
}
The code does the same thing, but there are a few differences in which actions are done more. The array access in version two with [] may be less efficient under the hood than having the forEach
pre-load the items. And the creation of a function context for each iteration will be more expensive in version One because it will open a closure track all the variables involved each time.
Lets look at that last case, the creation of a closure and function context.
- Create a scope record in memory
- Retain the closure until all variables are no longer in use
By contrast, the for loop body is only ever in one place, and references one scope.
So in terms of comparing performance we have a competition of two things.
- Array access vs pre-loaded execution (this is because the item.forEach is predictable, whereas [i] can be any out of order access of an element, making it harder to optimize)
- or Function context creation for each item
It's highly likely (depending on the JavaScript implementation) that the array access being random is a far lower cost than the creation of a closure for each item.
You will also notice I've pulled out the length into a variable len
, this is because for certain collections (such as DOM Element Collections), the length property is actually a calculation. If you have a list of DOM elements, accessing the length property will climb the elements and calculate the total length, every time the loop iteration runs, pulling out a variable will avoid this.
While the .forEach
looks fantastic and is easy to look at, lets consider how it scales with human effort in how it is modified.
Lets introduce the idea of an early out, lets say we only need to run until we find 3 items.
let elidgableThree = [];
const NEEDED_COUNT = 3;
const items = [objA, objB, objC, objD];
function doStuff(obj){
if(obj.done){
elidgableThree.push(obj);
}
return elidgableThree.length >= NEEDED_COUNT;
}
// Version One - with a closure
items.forEach((obj) => doStuff(obj));
// Version Two - with a for loop
for(let i = 0, len = items.length; i < len; i++){
let allDone = doStuff(items[i]);
if(allDone){
break;
}
}
Now that we have a control flow return value, it's much easier to control our code with the for
loop than the forEach
. This is another example of how convenience that hides ugly details can take away flexibility.
Version Two is unquestionably uglier, and also more adjustable to a variety of circumstances.
Posted on April 1, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.