This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm trying to create "dynamic" cascading dropboxes, and I almost have it done. However, I'm running into an issue dealing with adding a .change() listener onto every variable inside of an array that contains the <select> field ids.
So if I use:
fields[0].change(function() {populateFields(1, 0, xml);});
fields[1].change(function() {populateFields(2, 1, xml);});
The code works perfectly. However, I would prefer to use something like this:
for (i=1; i<numberOfFields; i++){
p = i-1; current = i;
fields[p].change(function() {populateFields(current, p, xml);});
}
So that I can have a variable number of fields, because the current code is limited to three fields. The for loop currently works, but doesn't work after the second field is entered.
Any help would be appreciated.
NOTE: This is not a question about variables or passing variables into functions, but rather adding a event listener to an array. The marked answer was the correct answer.
You can use Array.prototype.forEach(), and in the function that you pass in you can put a guard for the case where you are processing the first element:
fields.forEach(attachChangeHandler);
function attachChangeHandler(field, i) {
if (i === 0) { return; }
field.change(function() {
populateFields(i + 1, i, xml);
});
}
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 1 year ago.
I am using clippy.js to make the characters in the viewport speak through a ballon. I would like to have the characters speak texts in a sequence by passing elements in an array that contains the different texts with a delay in between speaking the texts. For this I am using a for loop with a specific delay the between passings of the text. That part works well using setTimeout(), but, likely because of the asynchronous nature of JS, only the last text variable is passed to the function. Here is the code snippet:
for (n=0;n<4;++n){
x = tmptext[n];
setTimeout(() => {agent.speak(x);}, 5000 + n*5000);
}
tmptext[] is an array that contains the different texts, and cagent.speak() is the function for making the character speak a text, which otherwise works well. The problem is that the character only speaks the last text in the array 4 times although it does so with the proper timing between them. Clearly, the function is executed 4 times using only the value of the variable x as assigned to it in the last iteration in the for loop (which is the value of x=tmptext[3] in this case).
If I use the tmptext[] array directly, I get no text at all passed from this array into the function. For instance,
for (n=0;n<4;++n){
setTimeout(() => {agent.speak(tmptext[n]);}, 5000 + n*5000);
}
Here, no text is passed as confirmed by using alert in the cagent.speak() function to monitor this, and indeed the balloon appears empty 4 times. This is because the value of n that is being used is of its last value in for iteration which makes it 5 and beyond the scope of the array.
The asynchronous nature of JS seems to be the problem here and I am not familiar in dealing with it. How do I pass the value of each element in the array into the function with the proper timing in between? Would appreciate help here.
This code solved the problem:
for (n=0;n<4;++n){
let x = tmptext[n];
setTimeout(() => {agent.speak(x);}, 5000 + n*5000);
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I've been poking around and I can't seem to find an answer to this question.
I have a linked list that I need to access when certain objects on my page are clicked. I made a get(id) method that loops through this linked list until it finds the node matching the id. At the moment there are 3 items in my list and therefore ids 0, 1, and 2. The set method manipulates html on the page based off of information from the linked list.
Here is the code for my click listener where secondaryFeatured is an array of JQuery objects:
for(var i = 0; i < secondaryFeatured.length; i++) {
secondaryFeatured[i].click(function() {
set(list.get(i));
});
}
DoubleLinkedList.prototype.get = function(id) {
while(this.head.id != id) {
this.next();
}
return this.head;
}
When I click it gets stuck in an infinite loop in my get function searching for the id "3" when there are only 3 objects in my (last object has index of 2).
Using get() you are trying to get the element that was clicked. For that you can simply do this.
for(var i = 0; i < secondaryFeatured.length; i++) {
secondaryFeatured[i].click(function() {
set(list.$(this));
});
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
In JavaScript, what are specific reasons why creating functions within a loop can be computationally wasteful?
(2 answers)
Closed 8 years ago.
I checked over my script the other day with JSFiddle and got a warning on one of the lines: Don't make functions within a loop.
for (x = 0; x < 10; x++) {
if (moment(now) > moment(then)) {
doIt(x); // do it now
} else {
timeTillEnd = moment(then) - moment(now);
setTimeout(function () {
doIt(x); // do it later
}, timeTillEnd); // <-- flagged here
}
}
Why shouldn't I make functions within a loop in Javascript?
Also: Could the usage of a function in the particular situation shown here be problematic?
What you are trying to do is probably wrong, the x variable might not be what you expect it to be. See the following link:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Creating_closures_in_loops.3A_A_common_mistake
And they are also relatively expensive to create.
Each function comes with the closure of the variables it uses, that is an unnecessary overhead if you are doing "normal imperative programming" and just want to make the code look clearer by defining inner functions for sub-tasks:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures#Performance_considerations
In your case, it seems that you actually need a function with its closure, since you are deferring some computation, but make sure that you do the proper value capture.
Because it can lead to unexpected closure behaviour (the captured variable will have the value assigned in the last iteration of the loop). You will also get a new instance of the function for each loop which is wasteful of resources.
Modern browsers take a third argument for setTimeout which is the argument to the function. See here. This also gets rid of the problems with closures.
This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 9 years ago.
Currently working on a piece of code that is supposed to add eventlisteners to images on a page in a dynamical way.
var images = document.getElementsByClassName("imageBox");
function imageZoomer(imageName) {
console.log(imageName);
}
for(var i = 0; i < images.length; i++) {
images[i].addEventListener("click", function(){imageZoomer(i)}, false);
}
However the i is showing different values then I expect. At first everything is going wel. It iterates just like it's suppose to do. But on my test page with 2 images the console log reveals '2' at both the images.
This happens because i keeps incrementing and by the time you click an image i is probably images.length - 1. You need a way to save the current i in each handler. You can store that value on each image (images[i].index = i; usually works) and retrieve it later but finding the object is a pain - it'd end up being something like:
images[i].index = i;
images[i].addEventListener("click", function(e){imageZoomer(e.target.index)}, false);
However, I've found the slickest way to do this is using an IIFE (read: iffy - Immediately Invoked Function Expression) to restrict the scope of i to whatever it was when the event was created. That looks something like:
images[i].addEventListener("click", (function(i){return function(){imageZoomer(i)}})(i))
IIFEs are super powerful. Take a look at Ben Alman's post: http://benalman.com/news/2010/11/immediately-invoked-function-expression/
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
So basically I make a bunch of buttons with JQuery, and want to give them all some common "onclick" function, but want said function to know unique information about the button that clicked it. Using the below method, the alert message prints "This bug makes me want to beat my head against a wall 10 times", regardless of the button pressed. What the bizz, man? Thanks for your help!
function createButtons()
{
for(var i=0; i<10;i++)
{
$('#Mystical_land_of_buttons').append('<button class="'+i+'">'+i+'</button>');
$('button.'+i).click(function(){gimmeYoDigits(i);});
}
};
function gimmeYoDigits(id)
{
alert("This bug makes me want to beat my head against a wall "+id+" times.");
}
You need to use closure for this:
for(var i=0; i<10;i++) {
// ...
(function(i) {
$('button.'+i).click(function(){gimmeYoDigits(i);});
})(i);
This is because the variable i is no longer what you expect when the click function is executed. You could do the following:
var btn = $('<button>'+i+'</button>');
$('#Mystical_land_of_buttons').append(btn);
btn.data('id',i).click(function(){gimmeYoDigits($(this).data('id'));});