JavaScript Infamous Loop Issue

Javascript infamous Loop issue?

Quoting myself for an explanation of the first example:

JavaScript's scopes are function-level, not block-level, and creating a closure just means that the enclosing scope gets added to the lexical environment of the enclosed function.

After the loop terminates, the function-level variable i has the value 5, and that's what the inner function 'sees'.

In the second example, for each iteration step the outer function literal will evaluate to a new function object with its own scope and local variable num, whose value is set to the current value of i. As num is never modified, it will stay constant over the lifetime of the closure: The next iteration step doesn't overwrite the old value as the function objects are independant.

Keep in mind that this approach is rather inefficient as two new function objects have to be created for each link. This is unnecessary, as they can easily be shared if you use the DOM node for information storage:

function linkListener() {
alert(this.i);
}

function addLinks () {
for(var i = 0; i < 5; ++i) {
var link = document.createElement('a');
link.appendChild(document.createTextNode('Link ' + i));
link.i = i;
link.onclick = linkListener;
document.body.appendChild(link);
}
}

JavaScript closure inside loops – simple practical example

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]();
}

The Infamous Loop Opportunity

The problem in your code is that it doesn't work.

link.onclick = Outside(i);

doesn't bind a function, as Outside(i) returns undefined.

A similar, working solution would be to define Outside as

function Outside(i) {
return function() {
alert(i);
}
};

it would work for the same reason your second code works : calling the function Outside creates a new scope for the variable i (it's called a closure).

Any solution you'll build from your loop will probably involve more than just a function call : it'll need the declaration of a variable, either with the var keyword or as an argument of that function.

For loop function, unexpected output - always the same

The reason, as already mentioned, is that the scope of i is the same for both eventhandlers, and as such it will have the same value for both of them.

There is a couple of solution for this problem.

Solution 1: create a new scope via a immediate function

var pm = 2; 
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).mouseenter(function(instance){
return function() { alert(instance); };
}(i));
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/

Solution 2: use jQuerys data method to store the value

var pm = 2; 
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).data('instance',i).mouseenter(function(){
alert($(this).data('instance'));
});
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/1/

Solution 3: bind the instance number to the eventhandler

var pm = 2; 
for (var i = 0; i < pm; i++) {
$("#specialty_pm_"+i).mouseenter(function(instance){
alert(instance);
}.bind(null,i));
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/2/

Solution 3 has a few caveats - this is being bound as null, and thus it can no longer be used as a reference to the dom element, like jQuery eventhandlers nomrally do. Also bind isn't supported by older browsers, but this can be mitigated by usinga polyfill, a good one can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Solution 4: Be smart and use a delegate instead of binding event handlers in a loop

     $(document.body).on('mouseenter','.specialty_pm',function(){
alert($(this).data('id'));
});

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/4/

Solution 4 is the "right way" to do it, but it will require you to change the way you build your markup

Javascript for loop not running correctly

Closure:

var layer = new Kinetic.Layer();
for (var i=0 ;i<5; i++) {
(function(num) {
var tile = new Kinetic.Shape({
sceneFunc: function(context){ eval(tiles[num]); }
});
layer.add(tile);
}(i));
}

Issue JavaScript (JS) retriving data from firebase using a loop

You'll need a closure/self-invoking function to capture the value of srSn and srMv, as explained in Javascript infamous Loop issue? and What is the purpose of a self executing function in javascript?.

The simplest way to do that is to extract the code that interacts with Firebase into a named function and pass in the values of srSn and srMv:

$(document).ready(function(){
var db = firebase.database();
var pos = ['der','mid', 'izq'];
var pl = 1;
var dataT = ['sonido', 'mov'];
for( var pl = 1; pl<4; pl++) {
for(var i = 0; i < 3; i++){
var search = 'insti/planta'+pl+'/'+pos[i];
var srSn = 'pl'+pl+'-pos-'+pos[i]+'-data-sonido';
var srMv = 'pl'+pl+'-pos-'+pos[i]+'-data-mov';
updateUI(search, srSn, srMv);
}
}
});

function updateUI(search, srSn, srMv) {
db.ref(search).on("value",function(snap){ //WeMos1
sonido = snap.val().sonido;
mov = snap.val().mov;
document.getElementById(srSn).innerHTML = "Sonido = "+sonido;
document.getElementById(srMv).innerHTML = "Movimiento: "+mov.toString();;
}); //FIN WeMos1

}

Need clarification on the loop problem in closures

So if the getHelp() function is bound to the onfocus event handler, and you don't even focus the input fields, howe can the getHelp() function run?? And how does JAvaScript store all possible outcomes from that one little loop?

This is the crux of your question, and the answer lies here:

for(var i=0;i<helpText.length;i++){
var item = helpText[i];
alert(item.help);
// only to show value of "help" at this point, also to prove that "getHelp()" is
// being called even though the onfocus wasn't used
document.getElementById(item.id).onfocus = getHelp(item.help);
}

That's how the JavaScript interpreter stores all of the possible outcomes: Because you told it what they were. You're calling getHelp, which is generating a function, and then returning that function.

How this works is much simpler than it seems. :-) I go into it a fair bit here, but basically: When you call a function, something called an execution context is created. That's an object (JavaScript is massively object-oriented, right down to the interpreter level). In that execution context object, there's something called the variable object. It holds all of the variables for the execution context, as properties. This includes the arguments to the function, all the vars within the function, and any declared functions as well (you don't have any declared functions in your example, so we can ignore that; you have only function expressions, which is fine). Any function declared or defined by expression within an execution context has an enduring reference to the variable object for that execution context, and uses it to resolve variable references when it's called.

So: In your loop, when you call getHelp, an object is created storing the data related to that call. That object is bound to the function that you're creating within the call and storing on the onfocus handler (that's the data that the function closes over [which is why it's called a closure]). When/if the handler is called, that's how the references that function holds are resolved, against properties on that object.

More reading: Closures are not complicated



Related Topics



Leave a reply



Submit