Odd Café

JavaScript and Scope VIII - Improved Closures

June 13, 2019 • 2 min read

In the previous article on JavaScript and Scope VII - More Closures you saw how we can use closures to help us avoid problems with blocks not having scope, especially in loops.

var funcs = [];

// Outer function creates new scope.
function makeLog (i) {
  var j = i;  // Declare inner function using new scope.
  return function () {
    console.log('My value: ', j);  }
}

for (var i = 0; i < 4; i++) {
  // Run makeLog, but the inner function doesn't run yet.
  funcs[i] = makeLog(i);
}

// Run each inner function.
funcs[1](); // logs 1
funcs[2](); // logs 2
funcs[3](); // logs 3

We can improve the code above because as you saw in JavaScript and Scope I - Intro, parameter names also use function scope. An inner function using an outer function scope is what closures depend on.

var funcs = [];

// Outer function creates new scope.
function makeLog (j) {  // Declare inner function using new scope.
  return function () {
    console.log('My value: ', j);  }
}

for (var i = 0; i < 4; i++) {
  // Run makeLog, but the inner function doesn't run yet.
  funcs[i] = makeLog(i);
}

// Run each inner function.
funcs[1](); // logs 1
funcs[2](); // logs 2
funcs[3](); // logs 3

All we did is remove the use of the intermediate variable j, and use the parameter to create the function scope we need for a closure.

We can "simplify" even further by using an Immediately Invoked Function Expression (IIFE).

var funcs = [];

for (var i = 0; i < 4; i++) {
  // The inner function doesn't run yet.
  funcs[i] = ((j) => () => console.log('My value: ', j))(i);}

// Run each inner function.
funcs[1](); // logs 1
funcs[2](); // logs 2
funcs[3](); // logs 3

Much shorter, but perhaps not as simple to understand. We use an outer arrow function that returns an inner arrow function. And we immediately run that outer arrow function with the value of i. In terms of how it works, it is exactly the same as the previous example using the makeLog function. Compare the two until you understand why.

A truly easy to understand way of avoiding the block scope problem with loops is using the let keyword to declare i in the for loop. Remember the let keyword will create a new scope for blocks, as you saw in JavaScript and Scope V - let and const.

var funcs = [];

for (let i = 0; i < 4; i++) {
  funcs[i] = function () { 
    console.log('My value: ', i);
  };
}

funcs[1](); // logs 1
funcs[2](); // logs 2
funcs[3](); // logs 3

Don't dismiss closures just because we can create block scope with let. You will still see variations on the closure-based solutions above, especially in pre-ES6 code. And the very popular JavaScript module pattern is just one example of the power of closures where let is not going to help you at all.