Refactoring with let Block Scope

While I was refactoring a code challenge today, I learned something very cool about ES6 variable declarations via the let keyword vs. variable declarations via the var keyword. let is scoped to the nearest block while var is scoped to the nearest function. This is particularly important in the context of loops because let will create a new binding of a variable for each iteration; var will only create one binding.

When you have a callback inside a loop that relies on separate bindings at each step, using let saves you the headache of wrapping the callback in a closure to make copies of the variable at each increment. The canonical example is calling JavaScript’s setTimeout() function inside a loop to execute a function or code snippet after a specified period of time (delay) in milliseconds:

Let’s take a look at how this informed the refactoring of my code.

I was tasked with building a tool that takes a sequence of CSS styles as input and uses vanilla JavaScript to apply each step in the sequence to the DOM, animating an element over time.

The input consists of a 2D array of CSS styles, where each array has a time delay and any number of style objects.

To implement the animator, I looped over the input and set timers to update the style of an element in the DOM after a delay. let‘s block scope binding allowed me to refactor my code without a closure.

An integral part of the challenge is that each step is relative to the previous step. So it’s not enough to apply the styles at step 2 after 200 milliseconds (see input array above); step 2 must be applied after delay + delaySoFar milliseconds, or 300 milliseconds. Likewise, step 3 must be applied after 500 milliseconds.

To persist the cumulative delay, on line 29 above, I declare the variable delaySoFar outside of the loop. Inside of the loop, I set each step’s delay to the sum of the current delay (sequence[0]) and delaySoFarOn line 36, I invoke setTimeOut(), without a closure thanks to let‘s block scope binding. Inside each timer, I set the style of an element in the DOM after the current delay. Last, after the timer is set but before the next loop, I update delaySoFar. The result animates the element and each step’s delay is relative to the previous step.

I really enjoyed this code challenge. The efficiency of let‘s block scoping is nifty.