Event Handlers Inside a JavaScript Loop - Need a Closure

Event handlers inside a Javascript loop - need a closure?

You do, indeed, need to implement a closure here. This should work (let me know - I didn't test it)

var blah = xmlres.getElementsByTagName('blah');
for(var i = 0; i < blah.length; i++) {
var td = document.createElement('td');
var select = document.createElement('select');
select.setAttribute("...", "...");
select.onchange = function(s,c,a)
{
return function()
{
onStatusChanged(s,c,a);
}
}(select, callid, anotherid);
td.appendChild(select);
}

Understanding javascript closure with event handlers

Most of this will work the way you expect it to, except this part:

button.addEventListener("click", function (e) {
alert(i);
}, false);

One might expect that each addEventListener gets three parameters:

  1. "click"
  2. The result of the function
  3. false

If that were the case, the five event listeners would look like this:

button.addEventListener("click", alert(0), false);
button.addEventListener("click", alert(1), false);
button.addEventListener("click", alert(2), false);
button.addEventListener("click", alert(3), false);
button.addEventListener("click", alert(4), false);

However, it isn't the result of the function that is set as the second parameter, it is the function itself. So in actuality, here are your five event listeners:

button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);
button.addEventListener("click", function(e){alert(i);}, false);

As you can see, the second parameter for all of them is the following function:

function(e){
alert(i);
}

So when you click on one of these buttons, the above function will execute. So you click on a button and the function executes, here's the operative question: what is the value of i? The context is important here. The context is not the middle of the loop, the context is that you've clicked on the button and fired this function.

A smart guess would be that i is undefined in this context. But there's this tricky thing that happens when a function is created inside another function: the sub-function has access to the variables in the parent function. So i is still available! So what is i right now? It's 5, because we've looped it up to 5, and it remains 5.

So when you hear people talking about closures, all they really mean is "sub-function." Sub-functions are tricky because they carry the variables of their parents with them in this way.

So what if you want the buttons to alert the number corresponding to the button clicked? You can do it if you use a function that is immediately executed, and therefore returns the result of the function, rather than the function itself. To do that, you just wrap your function expression in parens, and follow it with ();.

var one = function(){}      // one contains a function.
var two = (function(){})(); // two contains the result of a function

Let's play with this a little to see how it would affect the code.

We could make the second parameter of the event listener execute immediately like so:

button.addEventListener("click", (function (e) {
alert(i);
})(), false);

Then you would get five alerts, 0-4, immediately upon loading the page, and nothing when clicking, because the function didn't return anything.

That's not what we really want. What we actually want is to get the addEventListener to fire from within that loop. Easy peasy--just wrap it in a function that executes immediately:

(function(i){
button.addEventListener("click", function (e) {
alert(i);
}, false);
})(i);

Note that instead of (); we have (i); now, because we're passing the parameter i to it, but other than that it's all the same. Now the function is fired immediately, a.k.a. we're going to get the result of the function immediately. What is the result of this new immediately executing function? It actually changes depending on which iteration of the loop it is in. Here are the results of the function for each iteration of the loop:

button.addEventListener("click", function (e) {
alert(0);
}, false);
button.addEventListener("click", function (e) {
alert(1);
}, false);
button.addEventListener("click", function (e) {
alert(2);
}, false);
button.addEventListener("click", function (e) {
alert(3);
}, false);
button.addEventListener("click", function (e) {
alert(4);
}, false);

So you can see now that the five functions that will fire on click each have the value if i hardcoded in there. If you get this, you get closures. Personally, I don't understand why people overcomplicate the issue when they explain it. Just remember the following:

  1. Sub-functions have access to their parent's variables.
  2. Functions that are used as expressions (e.g. assigned to a variable or something, not defined like function newFunc(){}) are used as a function and not as the result of the function.
  3. If you want the result of the function, you can execute the function immediately by wrapping it in ( ... )();

Personally I find this a more intuitive way to understand closures without all the crazy terminology.

Closure needed for binding event handlers within a loop?

You are right about what you read in the other post.
You need to make a closure to bind the arguments to each single onclick handler:

$('#' + tabs[i]).bind(
'click',
(function(id) {
return function()
{
loadTabs(id, tabs);
};
})(id)
);

You might also want to look into currying.
In this example you might create a small helper function, which binds the first argument to a passed function and returns the new function.

function curry(func, arg1)
{
return function()
{
func(arg);
};
}

And then put it together like this:

$('#' + tabs[i]).bind(
'click',
curry(function(id) { loadTabs(id); }, id)
);

Note that my curry function does not match the definition of currying, because it ignores any other argument. But it should work for your case.

How to use closures to create event listeners in a Javascript for loop?

it doesn't work because

charElems[i].addEventListener('mouseover',function() {

(function(j) {mouseoverCheck(j);}(i));

});

addEventListener() is just assigning a handler and by the time that handler is called i will be 6.

you should return a handler from an IIFE

var charElems = document.getElementsByClassName('char');

for (var i=0; i < charElems.length; i++) {

charElems[i].addEventListener('mouseover', (function(temp) {

return function(){
var j = temp;
//mouseoverCheck(j);
console.log(temp);
}
}(i)));
}

Here is a fiddle: https://jsfiddle.net/qshnfv3q/

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

How to generate event handlers with loop in Javascript?

All of your handlers are sharing the same i variable.

You need to put each handler into a separate function that takes i as a parameter so that each one gets its own variable:

function handleElement(i) {
document.getElementById("b"+i).onclick=function() {
alert(i);
};
}

for(i=1; i<11; i++)
handleElement(i);

How to declare event handlers in a loop?

While the answers give you alternate/better ways of doing what you are looking for, you need a closure (anonymous self executing function)

for (var i = 0; i < 3; i++) {
(function (i) {
$('button:eq(' + i + ')').click(function () {
$('span').append((i + 1) + 'button was clicked');
});
}(i));
}

Check this fiddle: http://jsfiddle.net/RefT2/1/

Closure/callback in for loop

You need an additional wrapper function

onClick: (function(obj) {
return function() {
customFunction(obj);
this.transitionTo("someScreen");
};
})(response.myObject[i])

See this answer for an explanation:

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 [note: or, in this case, the name of the last property in response.myObject], and that's what the inner function 'sees'.

Issues with closure in for-loop (event)

An example of this problem and how to solve it in the general case is described in JavaScript closure inside loops – simple practical example.

However, jQuery allows you to pass additional data to an event handler (see the documentation), which is another way to solve this problem:

for (var i = 0; i < boxColors.length; i++){
$('<div/>', {
'class': boxColors[i]
})
.appendTo(toolboxSection1)
.click({color: boxColors[i]}, function(event){
// event.data.color
});
}


Related Topics



Leave a reply



Submit