Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript closure inside loops – simple practical example

var funcs = []; // let's create 3 functions for (var i = 0; i < 3; i++) {   // and store them in funcs   funcs[i] = function() {     // each should log its value.     console.log("My value: " + i);   }; } for (var j = 0; j < 3; j++) {   // and now let's run each one to see   funcs[j](); }

It outputs this:

My value: 3
My value: 3
My value: 3

Whereas I'd like it to output:

My value: 0
My value: 1
My value: 2


The same problem occurs when the delay in running the function is caused by using event listeners:

var buttons = document.getElementsByTagName("button"); // let's create 3 functions for (var i = 0; i < buttons.length; i++) {   // as event listeners   buttons[i].addEventListener("click", function() {     // each should log its value.     console.log("My value: " + i);   }); }
<button>0</button> <br /> <button>1</button> <br /> <button>2</button>

… or asynchronous code, e.g. using Promises:

// Some async wait function const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));  for (var i = 0; i < 3; i++) {   // Log `i` as soon as each promise resolves.   wait(i * 100).then(() => console.log(i)); }

It is also apparent in for in and for of loops:

const arr = [1,2,3]; const fns = [];  for(var i in arr){   fns.push(() => console.log(`index: ${i}`)); }  for(var v of arr){   fns.push(() => console.log(`value: ${v}`)); }  for(var f of fns){   f(); }

What’s the solution to this basic problem?

like image 709
nickf Avatar asked Apr 15 '09 06:04

nickf


People also ask

What is closure in JavaScript explain with an example?

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function.

What is closure in JavaScript & the practical use of the same?

In JavaScript, closures are the primary mechanism used to enable data privacy. When you use closures for data privacy, the enclosed variables are only in scope within the containing (outer) function. You can't get at the data from an outside scope except through the object's privileged methods.

What is closure in JavaScript for beginners?

In JavaScript, a closure is a function that references variables in the outer scope from its inner scope. The closure preserves the outer scope inside its inner scope. To understand the closures, you need to know how the lexical scoping works first.

What is closure loop?

Definition of closed loop : an automatic control system in which an operation, process, or mechanism is regulated by feedback.


1 Answers

Well, the problem is that the variable i, within each of your anonymous functions, is bound to the same variable outside of the function.

ES6 solution: let

ECMAScript 6 (ES6) introduces new let and const keywords that are scoped differently than var-based variables. For example, in a loop with a let-based index, each iteration through the loop will have a new variable i with loop scope, so your code would work as you expect. There are many resources, but I'd recommend 2ality's block-scoping post as a great source of information.

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

Beware, though, that IE9-IE11 and Edge prior to Edge 14 support let but get the above wrong (they don't create a new i each time, so all the functions above would log 3 like they would if we used var). Edge 14 finally gets it right.


ES5.1 solution: forEach

With the relatively widespread availability of the Array.prototype.forEach function (in 2015), it's worth noting that in those situations involving iteration primarily over an array of values, .forEach() provides a clean, natural way to get a distinct closure for every iteration. That is, assuming you've got some sort of array containing values (DOM references, objects, whatever), and the problem arises of setting up callbacks specific to each element, you can do this:

var someArray = [ /* whatever */ ]; // ... someArray.forEach(function(arrayElement) {   // ... code code code for this one element   someAsynchronousFunction(arrayElement, function() {     arrayElement.doSomething();   }); }); 

The idea is that each invocation of the callback function used with the .forEach loop will be its own closure. The parameter passed in to that handler is the array element specific to that particular step of the iteration. If it's used in an asynchronous callback, it won't collide with any of the other callbacks established at other steps of the iteration.

If you happen to be working in jQuery, the $.each() function gives you a similar capability.


Classic solution: Closures

What you want to do is bind the variable within each function to a separate, unchanging value outside of the function:

var funcs = [];  function createfunc(i) {   return function() {     console.log("My value: " + i);   }; }  for (var i = 0; i < 3; i++) {   funcs[i] = createfunc(i); }  for (var j = 0; j < 3; j++) {   // and now let's run each one to see   funcs[j](); }

Since there is no block scope in JavaScript - only function scope - by wrapping the function creation in a new function, you ensure that the value of "i" remains as you intended.

like image 76
harto Avatar answered Nov 05 '22 11:11

harto