Monday 15 January 2018

JavaScript closure inside loops – simple practical example

itemprop="text">

data-hide="false" data-console="true" data-babel="false">
class="snippet-code">

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:



data-hide="false" data-console="true" data-babel="false">
class="snippet-code">
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);
});
}

class="snippet-code-html lang-html
prettyprint-override">










or asynchronous code, e.g. using Promises:



class="snippet" data-lang="js" data-hide="false" data-console="true"
data-babel="false">

class="snippet-code-js lang-js prettyprint-override">// 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));
}





What’s
the solution to this basic problem?


class="post-text" itemprop="text">
class="normal">Answer





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





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



data-hide="false" data-console="true" data-babel="false">
class="snippet-code">
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.




/>



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.




/>



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 value of i where each value is scoped inside
the loop, so your code would work as you expect. There are many resources, but I'd
recommend rel="noreferrer">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.



No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...