This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
OK, I'm sure this is a super simple question but other answers I found were a bit too complex for me to understand. I want to create a series of buttons, each sending an alert of the loop iteration it was created in. However, every button sends an alert of "5", the value of i after the loop is finished. How do I make a copy of i instead of accessing the actual value? Is this passing by value or reference?
<span id="buttons"></span>
<script>
var buttons = document.getElementById("buttons");
for (var i = 0; i < 5; i++) {
var btn = document.createElement("button");
btn.onclick = function(){alert(i);}
btn.innerHTML = "Button " + i;
buttons.appendChild(btn);
}
</script>
JSFiddle
This is because you have a closure around the i variable that is declared outside of the function that you are using it inside of.
Closures can be difficult to understand and you can learn more about them here.
Change the variable declaration to use let instead of var so that each loop iteration will get its own i scoped to it.
// let causes the variable to have block level scope and with a loop
// that means that a separately scoped variable is created for each
// loop iteration. This allows each iteration to have its own unique
// value for the variable that doesn't share a higher level scope.
for (let i = 0; i < 5; i++) {
var btn = document.createElement("button");
btn.onclick = function(){alert(i);}
btn.innerHTML = "Button " + i;
document.body.appendChild(btn);
}
One option is to wrap the event handler in a IIFE (immediately invoked function expression).
In doing so, you are capturing and passing the value of i on each iteration.
Updated Example
(function (i) {
btn.onclick = function() {
alert(i);
}
})(i);
Another option would be to use the bind() method to pass the value of i on each iteration:
Updated Example
btn.onclick = function(index) {
alert(index);
}.bind(null, i);
However, a better option may be to set a data-* attribute on the elements on each iteration, and then in the event listener you can access that element's data attribute:
Updated Example
btn.dataset.index = i;
btn.addEventListener('click', function() {
alert(this.dataset.index);
});
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I have a question which might sound silly. In the code below there are 2 console.log(i) statements. I want to know why does the second console.log(i) statement returns value of 2 and not 1 as the former on the first iteration (i.e. 1st statement i=n, 2nd: i=n+1). Shouldn't both be equal to 1 until the end of the loop?
function toggleWrapper(){
var el1 = document.querySelectorAll('[class="tCell entryDesc"]');
for (var i = 1; i < el1.length; i++) {
console.log(i);
el1[i].addEventListener('click', function(ev){
console.log(i);
var el2=document.querySelectorAll('[class="additionalInfoContainer"]');
if (el2[i-2].clientHeight) {
el2[i-2].style.maxHeight = 0;
}
else{
el2[i-2].style.maxHeight = el2[i-2].scrollHeight +"px";
}
},
false);
}
}
The problem is that the variable i, within each of your addEventListener() functions, is bound to the same variable outside of the function. simply change your for loop to :
for (let i = 1; i < el1.length; i++)
In the loop with 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 fine.
i think is something in your code because if you try to make a for loop with two "console.log()" it doesn't do that
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
my problem has 2 sides to it.
I am trying to send a simple get value that is generated via a loop like so:
for(var x=0; x<del.length; x++) {
del[x].onclick = function () {
WORK(x);
}
}
Here is my frustrated WORK function
function WORK (x) {
var y = ids[x];
var url = "Delete.php?val=" + y;
window.location = url;
}
I know i just have to pass the value to the function...but if i set it up like that the page executes the function on load and doesn't wait for my click and as is it is now it will always pass an undefined value...what is going on here?
Variable hoisting + non-scoped variables in for loops.
Use .forEach:
Array.prototype.slice.call(del).forEach(function(elem, index) {
elem.onclick = ...
});
Or if you can't, use an immediately-invoked anonymous function:
for (var x = 0; x < del.length; x++) {
(function() {
var elem = del[x];
...
})();
}
When you iterate through the loop, there is only ever one x variable. It is not scoped to the for loop, and changes on each iteration (x++). When you trigger a click, the event handler is called, which in turn calls WORK with the value of x as an argument, which would've already been del.length by the time it runs.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I am using an event listener on a checkbox and I need to be able to pass in an index. Currently it is always saving the index as 2. The length of the groups array is 2.
for(var i = 0; i < groups.length; i++) {
var input1 = document.createElement('input');
input1.type = "checkbox";
input1.addEventListener('click', function () {
console.log(i);
}, false);
}
for(var i = 0; i < groups.length; i++) {
(function(){
var parent = document.getElementById("sidebar-wrapper");
var input1 = document.createElement('input');
input1.type = "checkbox";
input1.addEventListener('click', function () {
var j = i;
var iMinus1 = j-1;
console.log(j);
setSelectedGroups(this, groups[iMinus1]);
}, false);
})()
}
Your index is always being saved as two because the function is being called asynchronously. It's checking the index not at the time you DEFINE the event listener, but at the time it's being CALLED. This means that the loop has probably already been terminated by the time you call the function, so it sees the index at it's ultimate value of 2. What this code does is execute an immediately invoked function so that the index, at the time you define the event listener, is saved in a closure, and it refers back to that closure.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
JavaScript Puzzle: Scope
(6 answers)
Closed 7 years ago.
I hope the title makes sense.
I am using a for loop to create one or more $(document).on() elements. Inside each $(document).on() element created, I need it to call a function foo(currentIndex) where the currentIndex is the value of the index at the time of the .on() definition.
Fiddle: https://jsfiddle.net/xLbses7w/
JavaScript/jQuery:
var event = ['click', 'click'];
var element = ['#someId', '#someId2'];
for (i = 0; i < event.length; i++)
{
$(document).on(event[i], element[i], function ()
{
foo(i); // would like this to be the value of i when the function was created
});
}
function foo(arg)
{
alert(arg);
}
HTML:
<div id="someId">div1</div> <br/>
<div id="someId2">div2</div>
The problem: When I click on the element using the .on() function I have created, it uses the latest value of i (which in this case is 2).
Desired Behavior: When I click on div1, it should alert 0, and when I click on div2 it should alert 1 (the current indexes at the time of .on() definition.)
You can use .data() to store the index value
$(element[i]).data('index', i);
$(document).on(event[i], element[i], function () {
foo($(this).data('index'));
});
DEMO
You can create a closure:
for (i = 0; i < event.length; i++) {
(function (i) {
$(document).on(event[i], element[i], function () {
foo(i); // would like this to be the value of i when the function was created
});
})(i);
}
-DEMO-
Basic question, but I have been pounding my head for a bit so thought id bring it here.
html looks like this (edit, fixed the closing quotes)
<span class='deleteimage-119'>delete</span>
<span class='deleteimage-120'>delete</span>
<span class='deleteimage-121'>delete</span>
<span class='deleteimage-122'>delete</span>
<span class='deleteimage-123'>delete</span>
javascript/jquery looks like this
iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
The click functionality gets attached to each individual span, but the alert after clicking just shows the last item in the array.
You have a scoping issue. By the time the callback fires, place has the last value from the loop.
You need to create a new variable for the closure; one variable per iteration, each of which will then be "caught" by the closure and used in the callback.
It would be nice if the solution were this:
var iids = ['119','120','121','122','123']; // don't forget `var` please
for (var i=0; i<iids.length; i++) {
var place = iids[i]; // local variable?
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
Alas, Javascript has no block scope so there's still only one variable here called place, and it keeps getting updated as the loop runs.
So, you have to use a function instead:
var iids = ['119','120','121','122','123'];
function f(place) {
// NOW `place` is a local variable.
$(".deleteimage-" + place).click(function () {
alert(place);
});
}
for (var i=0; i<iids.length; i++) {
f(iids[i]);
}
There are neater ways to employ this function approach using closures and bound variables, and the other answers cover those neater ways quite well. My answer has focused on explaining the issue.
The issue is with scopes and closure.
In JS the scope is # function level and not block level.
Try this:
var iids = ['119','120','121','122','123'];
for (i=0; i<iids.length; i++) {
place = iids[i];
var clickFn = function(a){
return function(){
alert(a);
}
}(place);
$(".deleteimage-" + place).click(clickFn );
}
This is because the click is occurring after the loop is completed, so you're alerting place = iids[iids.length - 1]. In order to achieve the result you're looking for you need to create a function closure and pass place in as a parameter:
iids = ['119', '120', '121', '122', '123'];
for (i = 0; i < iids.length; i++) {
place = iids[i];
(function(_place) {
$(".deleteimage-" + _place).click(function() {
alert(_place);
});
} (place));
}
Inside the loop, you are binding the click event to those span, but the events are fired only after the loop is complete. So, it will always show the last value.
As others have mentioned, you have a scoping issue. Unless all of those classes have a unique meaning, I'd move the place value to a data attribute and leave deleteimage as the class name. Something like:
<span class='deleteimage' data-place='119'>delete</span>
<span class='deleteimage' data-place='120'>delete</span>
<span class='deleteimage' data-place='121'>delete</span>
<span class='deleteimage' data-place='122'>delete</span>
<span class='deleteimage' data-place='123'>delete</span>
$(".deleteimage").click(function() {
var place = $(this).data("place");
alert(place);
});
If the place values aren't unique values, then this answer doesn't apply.