This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
(This question is related to this and this but answers there haven't helped me figure out what's wrong in my case.)
I am trying to create an array of clickable elements where each element is bound to a separate instance of some object.
I've simplified the real code I'm working on as much as possible for this question here:
//----------
// Setup part
// SomeObject just holds a number
var SomeObject = function(number) {
this.number = number;
this.getNumber = function() {
return this.number;
};
};
// contains SomeObject(1) through SomeObject(9)
var someArrayContainingObjects = [];
for(var i=1; i<=9; i++)
{
someArrayContainingObjects.push(new SomeObject(i));
}
//----------
// Problem part
for(var y=0; y<3; y++)
{
for(var x=0; x<3; x++)
{
var obj = someArrayContainingObjects[y*3 + x]; // Creating new variable in the loop every time explicitly with var statement?
$("body").append(
$("<button />")
.text("Should output ("+obj.getNumber()+")")
.click(function() {
alert(obj.getNumber()); // Will always be 9
})
);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
I thought by explicitly using var obj = ... inside the loop I would create a new context/scope/however it's called for each anonymous click() callback function I'm creating – so that when I click one of the objects, the appropriate number of the respective SomeObject is alert()ed and not always the number of the last SomeObject the loop takes from the array.
Could someone please explain to me why this code snippet does not work as expected, and what to change to have the code function correctly?
To create closure scope in JavaScript you need to invoke a function. In JavaScript we can also invoke functions as soon as you declare them. They are called immediately invoked function expressions
This way you can preserve your x and y values in the scope of the IIFE.
for(var y=0; y<3; y++) {
for(var x=0; x<3; x++) {
(function (x, y) {
var obj = someArrayContainingObjects[y * 3 + x]
$("body").append(
$("<button />")
.text("Should output ("+obj.getNumber()+")")
.click(function() {
alert(obj.getNumber())
})
)
}(x, y))
}
}
Working codepen
Also, this is a big problem that people encounter when they try to write JavaScript as if it was a class based language. I would try to look into writing JS from a more functional perspective
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I have the following code, to add each element an event using addeventlistener:
var x, y, z;
elm = elm.normalize();
if(!isobj(elm) && iselm(elm)) {
elm = new Array(elm);
}
for(x in elm) {
(function() {
elm[x].addEventListener('click', function() {
alert(x);
});
})();
}
but when I click any element that added an event by the loop it always show the last index example, like when I click the element it show an alert with example text inside the alert box.
Here was the result of console.log(elm) after elm = elm.normalize():
[sample: input.sample.fld, example: input.example.fld]
isobj(elm) is a function to check if variable is an object, same like
iselm(elm) is a function to check if variable is an element.
Due to fix this, I'm trying to use, (function() { /* I put the addEventListener as above */ })(); inside the loop, but still not work.
I already make sure that x is always showing it index, but I didn't know why it always showing the last index in the event.
Please help.
By the time that line of the code is executed, the for-loop has finished.
For explanation:https://dzone.com/articles/why-does-javascript-loop-only-use-last-value
You can use let if your browser supports it. (See article for explanation and alternatives)
var x, y, z;
elm = elm.normalize();
if(!isobj(elm) && iselm(elm)) {
elm = new Array(elm);
}
for(x in elm) {
(function() {
let myX = x;
elm[myX].addEventListener('click', function() {
alert(myX);
});
})();
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I want to create multiple functions that do different things depending on what's written in a data-table and store them in an object for later use.
Let's look at this simple example:
var arrayPl = [];
function myFunction() {
var carNames = ["Volvo", "Nissan"];
var counter = 0;
for (var i in carNames) {
arrayPl[counter] = function() {
alert(carNames[i]);
};
counter++;
}
}
myFunction();
arrayPl[0]();
Here I wanted to create as many functions as there are car names and save them in an object arrayPl that I could call these functions from later.
Now obviously this example doesn't explain why I would need to do this - but at least it shows my problem, because the first function arrayPl[0](); gives me the second car name instead of the first.
Explaining the reason behind this is too complicated for me now and not that important (Dialogs in Adobe LiveCycle) - I just want to know if what I'm asking is possible in general and if so how.
I prefere this syntax :
var arrayPl = [];
function myFunction() {
var carNames = ["Volvo", "Nissan"];
carNames.forEach( function(element, i) {
arrayPl[i] = function() {
alert(carNames[i]);
}
})
}
myFunction();
arrayPl[0]();
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 8 years ago.
guys, really need for your helps. I got some code block as follows:
function readyToSubmit(answerPack, answerArr, len) {
for (var i = 0; i < answerArr.length; i++) {
var questionId = answerArr[i].id;
console.log(questionId);
// below is an database async operation
userStore.getDoc(id).then(function(doc) {
// if I console.log 'answerArr[i]' here, it will be undefined
// I know it's 'cause the 'i' here is answerArr.length, so it would be undefined
// I want my questionId differently, but it is always the last one in the array
// I know it's the closure issue, but don't really know how to handle it.
doc.questionId = questionId; // always the same one
answerPack.push(doc);
});
}
}
So, how can I exactly get what I want in every round, I mean different questionId, not always the last one. Many many thanks, :)
You could so somethink like ,
function readyToSubmit(answerPack, answerArr, len) {
for (var i = 0; i < answerArr.length; i++) {
var questionId = answerArr[i].id;
doasynch(questionId);
}
}
function doasynch(questionId) {
userStore.getDoc(id).then(function (doc) {
doc.questionId = questionId;
answerPack.push(doc);
});
}
Read
How Closure works
Closure inside loop issue
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
I have a dc.js program which runs fine when I give separate names to dc groups
var group1 = dateDimension.group().reduceSum(function(d) { return d.dd; });
var group2 = dateDimension.group().reduceSum(function(d) { return d.count; });
but when I do
var groups = {};
var columns = ["dd","count"];
for (var i = 0; i < columns.length; ++i) {
var col = columns[i]
groups[col] = dateDimension.group().reduceSum(function(d) { return d[col]; });
}
it only remembers the last column and replaces other charts with last chart.
How should I solve this issue
The problem here is one that comes up all the time in JavaScript. Your variable "col" is scoped to the function where that for loop lives, and so it's shared by the two anonymous functions passed into the "reduceSum()" function.
The solution is to interpose another function to provide a distinct copy of the column name. (Also, you should not use for ... in to iterate through arrays.)
for (var i = 0; i < columns.length; ++i) {
(function( columnName ) {
group[ columnName ] = dateDimension.group().reduceSum(function(d) { return d[columnName]; });
})( columns[i] );
}