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:
"click"
- The result of the function
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:
- Sub-functions have access to their parent's variables.
- 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. - 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
value5
[note: or, in this case, the name of the last property inresponse.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
Pass Props to Parent Component in React.Js
Differencebetween a User and a Guildmember in Discord.Js
Why Are Callbacks from Promise '.Then' Methods an Anti-Pattern
How to Get the Size of a JavaScript Object
When to Use Es6 Class Based React Components VS. Functional Es6 React Components
Using Rails 3.1, Where Do You Put Your "Page Specific" JavaScript Code
Const in JavaScript: When to Use It and Is It Necessary
How to Check If Object Property Exists with a Variable Holding the Property Name
How to Populate a Cascading Dropdown with Jquery
Are JavaScript Arrays Primitives? Strings? Objects
How to Redirect with JavaScript
Why Does Google Prepend While(1); to Their JSON Responses
How to Find Events Bound on an Element with Jquery
Prevent Double Submission of Forms in Jquery
Nodejs VS Node on Ubuntu 12.04