This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I wanted to solve this question posted as a public question on testdome. Each as[i] should be a function that does alert(i).
The code to be bug-fixed is this:
function registerHandlers() {
var as = document.getElementsByTagName('a');
for (i = as.length; i-- >= 0;) {
as[i].onclick = function() {
alert(i);
return false;
}
}
}
The solution I attempted is this:
function registerHandlers() {
var as = document.getElementsByTagName('a');
//made the loop variables more explicit
for (i = as.length-1; i >=0; i--) {
var x = i;
as[x].onclick = function() {
alert(x);
return false;
}
}
}
I though that variable i is persistent, so I kept its copy in variable x, and use variable x instead of i. But it does not solve the problem completely. Please let me know what is my misunderstanding.
Your i and x values are declared in exactly the same scope, so by the time the function is executed x will be its final value. You could create a closure like this:
function registerHandlers() {
var links = document.getElementsByTagName('a');
for (var i = 0, len = links.length; i < len; i += 1) {
links[i].onclick = generateHandler(i);
}
function generateHandler (index) {
return function () {
alert(index);
return false;
}
}
}
Related
I wrote a simple javascript code. My for loop iterates a "let" declared variable, i between 0 and 2. A function gets declared within the loop only when i == 2. The function has to return the value of i variable. When I call this function from outside the loop, the function returns the value of i = 2 (which is natural for a block scope variable i. However, when I rewrite the loop code as its non-loop equivalent code-block, the function (still called from outside the block) returns the vale of i = 3. What is going on?
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo()); //returns 2
// loop equivalent
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function() {
return i;
}
i = 3;
}
console.log(printNumTwo()); // returns 3
Your example is bad because your loop is not counting after 2. So If your loop looks like i <= 3:
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
You would get exactly same result as your non-loop example and that's because of closure in javascript but return breaks for loop. Your function is saving reference to that variable from outside scope.
It's because you're actually setting the function to return the value 3 because of the non-loop environment. You should change the loop a little, adding another variable, but first make your function look like this:
printNumTwo = function() {
return num;
}
And in your simulated loop:
i = 2;
num = i;
printNumTwo = function() {
return num;
}
i = 3;
In your non loop based code, printNumTwo is not executed at the same point of its declaration and so the value of i is updated before it is executed so the value 3 is returned.
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function () {
return i;
}
i = 3;
}
console.log(printNumTwo());
but if you run the following code, it should print 2 since it is executed before value if i is set to 3
{
let i = 0;
i = 1;
i = 2;
printNumTwo = (function() {
console.log(i);
})()
i = 3;
}
Note: return in for loop breaks the further execution of the loop, so even if your first code had i <= 3 as its breaking condition, it will return 2.
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo())
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
// when references 'i' in this function, 'i' goes to the global scope.
return i;
};
// set the value 3 for 'i' in the global scope
i = 3;
}
console.log(printNumTwo()); // return 3;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 0
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 0;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
let i = 0;
i = 1;
i = 2;
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 2
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 2;
I appreciate all the answers I got to my question. All pointing to the case of how a function, when called, handles the environments in which it was both called and created. I read this useful explanation in the book "Eloquent JavaScript" and think it would be good to share it,
"A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called."
~ Eloquent_JavaScript/Closure
This question already has answers here:
How do JavaScript closures work?
(86 answers)
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have code on JavaScript.
var a = [];
for (var i = 0; i < 5; i++) {
a[i] = function () {
alert(i);
};
}
a[2]();
If I invoke a[2]() I expect to see a message with 2 but instead of this I see 5.
To fix it I can rewrite it like this:
for (var i = 0; i < 5; i++) {
(function (v) {
a[i] = function () {
alert(v);
}
})(i)
}
But I cannot understand how does it work. So why I need to wrap my function code to closure?
This question already has answers here:
Python while loop conversion to Javascript [duplicate]
(2 answers)
Closed 6 years ago.
In the for loop i'm iterating through the array of strings and meanwhile changing the src of image but, setTimeout method is not holding myfun for 3000 ms because of this the for loop just iterate in a blink of any eye to the last string of array. Where I'm going wrong?
<script>
function myfunction(){
var arr = ["mind.jpg","images.jpg","external.jpg"];
var image = document.getElementById("IMAGE");
for(var i =0;i<arr.length;i++)
{
setTimeout(myfun,3000);
image.src = arr[i];
}
}
function myfun(){
}
</script>
If you want to make something like slideshow, you need to do it like this:
function myfunction() {
var arr = ["mind.jpg","images.jpg","external.jpg"];
var image = document.getElementById("IMAGE");
var i = 0;
function next() {
image.src = arr[i];
if(++i === arr.length) {
i = 0
}
}
setInterval(next, 3000)
}
for (var i = 0; i < arr.length; i++) {
myfun(i);
}
function myfun(i) {
setTimeout(function() {
image.src = arr[i];
}, 3000 * i);
}
for (var i = 0; i < 5; i++) {
myfun(i);
}
function myfun(i) {
setTimeout(function() {
document.write(i + '<br>');
}, 1000 * i);
}
This question already has an answer here:
Why is the loop assigning a reference of the last index element to? [duplicate]
(1 answer)
Closed 8 years ago.
Can someone please tell me how can I grab i properly? By now, it's just displaying the same number (the value of a.length when clicking on any anchor element.
var a = document.getElementsByTagName('a');
for (i = 0, j = a.length; i< j;i++) {
a[i].onclick = function() {
console.log(i); //display a.length in all anchors
return false;
}
}
var a = document.getElementsByTagName('a');
for (i = 0, j = a.length; i< j;i++) {
a[i].idx = i;
a[i].onclick = function() {
console.log(this.idx);
return false;
}
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
JSFiddle
var arr = [ [0], [1], [2], [3] ];
for ( var i = 0; i < arr.length; i++ ) {
$('#btn-' + i).click(function() {
console.log(i);
});
}
When I'm clicking on corresponding button console.log always shows me last iteration instead of the current iteration. Why?
Try creating a closure, In other words, create a scope per iteration. Now in your code all the event handlers are created in a single scope and the i inside of that scope would get updated instantly to 4. So as a result, when you clicking on all the buttons the result would be same. That is the updated one of i
for ( var i = 0; i < arr.length; i++ ) {
var j = function(x) {
$('#btn-' + x).click(function() {
console.log(x);
});
}
j(i);
}
DEMO
Because of closure! For that you can do this:
for (var i = 0; i < arr.length; i++) {
(function(n) {
$('#btn-' + i).click(function() {
console.log(n);
});
})(i);
}
DEMO