Explain Codes LogoExplain Codes Logo

Javascript closure inside loops – practical use cases

javascript
closure
javascript-loops
async-functions
Alex KataevbyAlex Kataev·Sep 20, 2024
TLDR

Here's how to use let in for-loops to properly deal with index in your closures:

for (let i = 0; i < 5; i++) { setTimeout(() => console.log(i), i * 100); // This correctly logs each index because let bindings exist per iteration }

Alternatively, use an IIFE to retain the current value in closures:

for (var i = 0; i < 5; i++) { ((j) => { // This function "traps" the current value of i setTimeout(() => console.log(j), j * 100); // j logs as expected; stay tuned for magic! })(i); }

For JavaScript closures in loops, implementing certain practices can help you avoid pitfalls and keep your code order.

Using let for proper iteration value capture

In a loop, let creates a unique scope per iteration, solving most closure-related problems:

for (let i = 0; i < 5; i++) { someAsyncFunction(() => console.log(i)); // with 'let', we're playing safe! }

Because of let's block scope, each callback receives its own i, which results in each value of i logging correctly.

Function factories for scope battles

A function factory can produce another function that, in turn, uses the loop variable effectively:

function createFunction(val) { // This function factory creates well-behaved workers return function() { console.log(val); }; } for (var i = 0; i < 5; i++) { someAsyncFunction(createFunction(i)); // No worries! We've got a factory working for us. }

The current value of i is stored each time createFunction runs.

Operate .bind() like a pro

Take advantage of .bind() to specify certain values in a function:

for (var i = 0; i < 5; i++) { someAsyncFunction(console.log.bind(console, i)); // .bind(): Because spending hours debugging isn't my hobby. }

The magic with .bind() is that it can set both the this context and predefine the arguments, effectively capturing the current i.

Handle closures with forEach and jQuery.each

Array.prototype.forEach and jQuery.each manage loop variables correctly thanks to closures. Each iteration calls the callback with the current element (and optionally the index):

['a', 'b', 'c'].forEach((elem, index) => { someAsyncFunction(() => console.log(index)); // What closure problem? I only see solution here! });

Because of this, the right index logs without any extra measures to manage the scope.

Mastering closures in loops – advanced cases

Handling quirks in old browsers

While let handles loops properly in modern browsers, some early versions of Internet Explorer or Edge can differ. When dealing with old browser support, use tools like Babel for transpiling code.

Using named functions for efficient closure handling

For performance optimization, define functions outside the loop and bind the required properties:

function handlerFactory(index) { // It's like having your very own action figure factory return function handler() { // Each worker from the factory knows what to do console.log('Button', index, 'clicked'); }; } for (var i = 0; i < buttons.length; i++) { buttons[i].onclick = handlerFactory(i); // Go, Power Rangers! }

Libraries that assist with closures

Utilize _.partial from lodash/underscore to bind arguments without altering this context if these libraries are used in the project:

for (var i = 0; i < 5; i++) { someAsyncFunction(_.partial(console.log, i)); // We don't need magic, we got lodash! }

The right i value is used at the function call.

ES6 const and let to handle block scope

Should you create a variable within a loop, const and let ensure the declaration retains the state on each iteration, solving closure-related problems:

for (let options of optionArray) { someAsyncFunction(() => doSomethingWithOptions(options)); // These aren't your ordinary options! }