Javascript - Avoiding (the effects of) closures [duplicate] - javascript

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

addEvent type mismatch IE8

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.

Javascript how return works

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/

Javascript function doesn't receive an argument correctly [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
JavaScript closures and variable scope
Assign click handlers in for loop
I have this script:
var MyClass = {
MyArray: new Array(0, 1, 2, 3, 4),
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
var cen = document.getElementById("cen_" + i); // It is an img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc1(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}
},
MyFunc2: function(id) {
alert(id);
}
}
My problem is that, at this line :cen.onclick = function() { MyClass.MyFunc1(i); }; the argument sent to MyFunc2 is always -1. The MyFunc1 function should create four images, each one with an onclick event. When you click on each image, the MyFunc2 function should show the corresponding i value. It looks like the i value is not "saved" for each event and image element created, but only its "pointer".
Thanks!
You should be familiar with the concept of JavaScript closures to understand why this happens. If you are, then you should remember that every instance of the
function() { MyClass.MyFunc1(i); };
function closure contains i's value of -1 (since it is the final value of this variable after the entire loop finishes executing.) To avoid this, you might either use bind:
cen.onclick = (function(i) { MyClass.MyFunc1(i); }).bind(null, i);
or use an explicitly created closure with the proper i value.
It's a normal case and misunderstand of closures, see this thread and you may get some clue, the simply way to fix this problem is to wrap your for loop body with an Immediate Invoked Function Expression
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
}(i));
}
}
You are capturing a variable that changes inside the loop, so you always get the last value of i.
You can easily fix that by creating a closure:
MyFunc1: function() {
var i = 0;
for (i = MyClass.MyArray.length - 1; i>=0; i--) {
(function(i) {
var cen = document.getElementById("cen_" + i); // An img element
cen.src = "col.png";
cen.className = "cen_act";
cen.onclick = function() { MyClass.MyFunc2(i); };
} else {
cen.src = "no.png";
cen.className = "cen";
cen.onclick = null;
}
})(i);
}
},

Accessing variables w/in complete function

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

Javascript: closure of loop?

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.

Categories

Resources