I would like to do the something along the following:
for (var i = 0; i < 10; ++i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
The problem with this is that I always get the final value of i because Javascript's closure is not by-value.
So how can I do this with javascript?
One solution, if you're coding for a browser that uses JavaScript 1.7 or higher, is to use the let keyword:
for(var i = 0; i < 10; ++i) {
let index = i;
createButton(x, y, function() { alert("button " + index + " pressed"); }
}
From the MDC Doc Center:
The let keyword causes the item
variable to be created with block
level scope, causing a new reference
to be created for each iteration of
the for loop. This means that a
separate variable is captured for each
closure, solving the problem caused by
the shared environment.
Check out the MDC Doc Center for the traditional approach (creating another closure).
for(var i = 0; i < 10; i++) {
(function(i) {
createButton(function() { alert("button " + i + " pressed"); });
})(i);
}
Note that JSLint doesn't like this pattern. It throws "Don't make functions within a loop.".
Live demo: http://jsfiddle.net/simevidas/ZKeXX/
Create a new scope for the closure by executing another function:
for(var i = 0; i < 10; ++i) {
createButton(x,y, function(value) { return function() { alert(...); }; }(i));
}
http://www.mennovanslooten.nl/blog/post/62
You need to put the closure into a separate function.
for(var dontUse = 0; dontUse < 10; ++dontUse) {
(function(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
})(dontUse);
}
Thise code creates an anonymous function that takes i as a parameter for each iteration of the loop.
Since this anonymous function has a separate i parameter for each iteration, it fixes the problem.
This is equivalent to
function createIndexedButton(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
for(var i = 0; i < 10; ++i) {
createIndexedButton(i);
}
for(var i = 0; i < 10; ++i) {
createButton(x, y, (function(n) {
return function() {
alert("button " + n + " pressed");
}
}(i));
}
The anonymous function on the outside is automatically invoked and creates a new closure with n in its scope, where that takes the then current value of i each time it's invoked.
Related
I have a small for loop, but it is skipping even count , what am I missing ?
var i = 0;
function myLoop() {
setTimeout(function() {
//code below
console.log(Date() + ' and count is ' + i++);
//code above
if (i < 20) {
myLoop();
}
}, i++)
}
myLoop()
i++ is equal i = i + 1; But when you call console.log(i++), first thing wil be console.log with Old value, and after increment your value.
You raise the timeout with every iteration by on millisecond. Is that what you wanted?
https://www.w3schools.com/jsref/met_win_settimeout.asp
setTimeout(function, milliseconds, param1, param2, ...)
var i = 0;
function myLoop() {
setTimeout(function() {
//code below
console.log(Date() + ' and count is ' + i++);
//code above
if (i < 20) {
myLoop();
}
}, 1)
}
myLoop()
Your i++ in the console.log statement is modifying your i variable.
i++ is equal to i = i + 1.
Replace i++ with i, which will evaluate correctly as postfix increment will return the value before the modification which would just be i.
This works:
var i = 0;
function myLoop() {
setTimeout(function() {
//code below
console.log(Date() + ' and count is ' + i);
//code above
if (i < 20) {
myLoop();
}
}, i++)
}
myLoop()
Can also be done by adding another variable for the console.log
var i = 0;
var t = 0;
function myLoop() {
setTimeout(function() {
//code below
console.log(Date() + ' and count is ' + t++);
//code above
if (t < 20) {
myLoop();
}
}, i++)
}
myLoop()
You have i++ twice in your code. First you increment it when you call setTimeout at the bottom of your code and then you display the uneven value via console log and increment i. Change your logging to
console.log(Date() + ' and count is ' + i);
I am getting a TYPE: Mismatch error in IE8 with the following code.
function showTabs() {
for (var i = 0; i < tabs.length; i++) {
tabs[i].style.display = "inline-block";
if (tabs[i]) {
console.log(tabs[i] + " " + i);
}
}
}
function showThumbBoxes() {
for (var i = 0; i < thumbsContainers.length; i++) {
thumbsContainers[i].style.display = "block";
if (thumbsContainers[i]) {
console.log(thumbsContainers[i] + " " + i);
}
}
}
function loadImages() {
for (var i = 0; i < lazyImages.length; i++) {
if (lazyImages[i].getAttribute("data-src")) {
lazyImages[i].src = lazyImages[i].getAttribute("data-src");
if (lazyImages[i]) {
console.log(lazyImages[i] + " " + i);
}
}
}
}
function hideContainers() {
for (var i = 0; i < hiddenContainers.length; i++) {
hiddenContainers[i].style.display = "none";
if (hiddenContainers[i]) {
console.log(hiddenContainers[i] + " " + i);
}
}
}
function setUpPage() {
showTabs();
showThumbBoxes();
loadImages();
hideContainers();
}
if (window.addEventListener) {
window.addEventListener("load", setUpPage())
} else {
window.attachEvent("load", setUpPage()); <<< Here seems to be causing issues.
}
I have steppped through the code and it goes through everything correctly and everything gets loaded to the page. After I step through the last curly brace of setUpPage function, it is back on the attachEvent("load", setUpPage()); When I click step through again, I get the mismatch error. Not sure what is going on but because of the error the rest of my script will not load.
Anyone have an idea?
With attachEvent you need to add on + name of the event, so the event will be called onload
UPDATE
Also the second parameter of both of the event listeners, are callbacks, so they get executed when the event is triggered.
To be able to achieve that, you need to remove the parenthesis of the function call.
I actually want to update my previous question Javascript understanding return because the code below is quite similar to the previous one but since that question was answered already I decided to post this. The code of my previous questions works fine already but I want to satisfy some of my curiosities so I experimented the code and moved the return namePosition,
function positionIdentifier(name, nameArray) {
var namePosition;
for (i = 0; i < nameArray.length; i++) {
if (nameArray[i] == name) {
namePosition = function() {
alert("Your name is in position number " + (i + 1));
}
}
}
return namePosition;
}
name1Array = ["look", "sky", "walk", "kier"];
positionIdentifier("walk", name1Array)();
Why does it alert the wrong position (i+1)? Instead it alerts the final position which is the length of the array.
You forgot to use break statement here is correct code:
<script>
function positionIdentifier(name, nameArray) {
var namePosition;
for (i = 0; i < nameArray.length; i++) {
if (nameArray[i] == name) {
namePosition = function () {
alert("Your name is in position number " + (i + 1));
};
break;
}
}
return namePosition;
}
name1Array = ["look", "sky", "walk", "kier"];
positionIdentifier("walk", name1Array)();
</script>
That my friend is what is called a closure in javascript.
function() {
alert("Your name is in position number " + (i + 1));
}
When positionIdentifier function is invoked, i has the last value from the for loop.
To fix this you need to do this
function positionIdentifier(name, nameArray) {
var namePosition;
for (i = 0; i < nameArray.length; i++) {
if (nameArray[i] == name) {
/* now this will keep the correct value of i */
namePosition = (function(i) {
return function(){
alert("Your name is in position number " + (i + 1));
}
})(i)
/* now this will keep the correct value of i */
}
}
return namePosition;
}
Here is a working fiddle https://jsfiddle.net/uxyot51b/
I would like to do the something along the following:
for (var i = 0; i < 10; ++i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
The problem with this is that I always get the final value of i because Javascript's closure is not by-value.
So how can I do this with javascript?
One solution, if you're coding for a browser that uses JavaScript 1.7 or higher, is to use the let keyword:
for(var i = 0; i < 10; ++i) {
let index = i;
createButton(x, y, function() { alert("button " + index + " pressed"); }
}
From the MDC Doc Center:
The let keyword causes the item
variable to be created with block
level scope, causing a new reference
to be created for each iteration of
the for loop. This means that a
separate variable is captured for each
closure, solving the problem caused by
the shared environment.
Check out the MDC Doc Center for the traditional approach (creating another closure).
for(var i = 0; i < 10; i++) {
(function(i) {
createButton(function() { alert("button " + i + " pressed"); });
})(i);
}
Note that JSLint doesn't like this pattern. It throws "Don't make functions within a loop.".
Live demo: http://jsfiddle.net/simevidas/ZKeXX/
Create a new scope for the closure by executing another function:
for(var i = 0; i < 10; ++i) {
createButton(x,y, function(value) { return function() { alert(...); }; }(i));
}
http://www.mennovanslooten.nl/blog/post/62
You need to put the closure into a separate function.
for(var dontUse = 0; dontUse < 10; ++dontUse) {
(function(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
})(dontUse);
}
Thise code creates an anonymous function that takes i as a parameter for each iteration of the loop.
Since this anonymous function has a separate i parameter for each iteration, it fixes the problem.
This is equivalent to
function createIndexedButton(i) {
createButton(x, y, function() { alert("button " + i + " pressed"); }
}
for(var i = 0; i < 10; ++i) {
createIndexedButton(i);
}
for(var i = 0; i < 10; ++i) {
createButton(x, y, (function(n) {
return function() {
alert("button " + n + " pressed");
}
}(i));
}
The anonymous function on the outside is automatically invoked and creates a new closure with n in its scope, where that takes the then current value of i each time it's invoked.
for (var i = 0; i < 32; i++) {
var thisId = dropId+i;
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
}
In the above code example, by the time I get around to executing animate's complete function, thisId represents the last assigned value from the for loop NOT the value that I wanted to pass in for each iteration of the loop. Is there a way to get it to access the correct thisId?
JavaScript does not have block scope. You can create a new scope by calling a function. E.g.
for (var i = 0; i < 32; i++) {
(function(thisId) {
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
}(dropId+i)); // <-- calling the function expression and passing `dropId+i`
}
Variables declarations area always hoisted to the top of the function. So even if you have the declaration inside the loop, it is actually the same as:
var i, thisId;
for(...) {
thisId = dropId + i;
//...
}
Every closure you create inside the loop references the same thisId. It's like in Highlander: "There can be only one."
You need to use a closure around the current thisId.
for (var i = 0; i < 32; i++) {
var thisId = dropId+i,
complete = (function(id) {
return function() {
if ($("#p"+id).position().left == 1024) {
$("#p"+id).remove();
window.console.log("removed");
}
}
}(thisId));
$("#p"+thisId).animate({ left:"+=32px" }, complete);
}
Just wrapping what you had in an anonymous function should work:
for (var i = 0; i < 32; i++) {
(function() {
var thisId = dropId+i;
$("#p"+thisId).animate({ left:"+=32px" }, function(){
if ($("#p"+thisId).position().left == 1024) {
$("#p"+thisId).remove();
window.console.log("removed");
}
});
})();
}