This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 5 years ago.
I have a problem with this code.
var uls = document.getElementsByClassName("gal");
for (var i = 0; i < uls.length; i++) {
// impossible to pass uls[i] as callback argument (undefined in callback)
uls[i].addEventListener("click", function() { openSlideShowModal(uls[i].tagName);} );
}
I have the error : Uncaught TypeError: Cannot read property 'tagName' of undefined
I can't explained why uls[i] is undefined as it is global. I know that there's no use of it because it could be accessible by this in the callback. But i found this incomprehensible error by chance.
Moreover if i modify slightly i no longer have an error
var uls = document.getElementsByClassName("gal");
for (var i = 0; i < uls.length; i++) {
tagName = uls[i].tagName;
// impossible to pass uls[i] as callback argument (undefined in callback)
uls[i].addEventListener("click", function() { openSlideShowModal(tagName);} );
}
A bind this should help you out getting the function back into the same context where ever it is being called.
function() {
openSlideShowModal(uls[i].tagName);
}.bind(this)
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 3 years ago.
I would like to pass an argument to a callback function inside a for loop. The problem is the callback is called after the for loop ends and then the parameter does not exist anymore.
See the following code snippet:
function foo(x){
console.log(x)
}
for(var i = 0; i < arr.length; i++) {
var myObj = customObject()
myObj.on('click', function(){
foo(arr[i])
}
}
myObj is clicked on after the for loop ends which triggers an error:
TypeError: arr[i] is undefined
Is there any way to force the argument to be passed by its value at time of binding the callback function?
Try an IIFE:
myObj.on('click', function() {
(e => foo(e))(arr[i]);
});
The problem was that the click event was firing after the whole loop had finished - so i was arr.length, had not passed the conditional statement in the loop, and as such arr[i] was undefined.
You'd also need to use let instead:
for (let i = 0; i < arr.length; i++) {
let myObj = createObject();
myObj.on("click", function() {
(e => foo(e))(arr[i]);
});
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
I am trying to create event listeners for a pure JS dropdown menu. When I try to create the listeners using a for loop I get an error and it doesn't work, but when I create them manually using arrays they work perfectly. What am I missing?
var dropDown = document.getElementsByClassName('nav-sub__mobile-dropdown');
var subNavList = document.getElementsByClassName('nav-sub__list');
for ( i = 0; i < dropDown.length; i++) {
dropDown[i].addEventListener('click', function() {
subNavList[i].classList.toggle('nav-sub__list--active');
});
}
The above doesn't work, but if I create the event listeners manually using the arrays it does work.
dropDown[0].addEventListener('click', function() {
subNavList[0].classList.toggle('nav-sub__list--active');
});
dropDown[1].addEventListener('click', function() {
subNavList[1].classList.toggle('nav-sub__list--active');
});
dropDown[2].addEventListener('click', function() {
subNavList[2].classList.toggle('nav-sub__list--active');
});
When I use the for loop I get the following error code in my console.
Uncaught TypeError: Cannot read property 'classList' of undefined
UPDATE SOLVED PROBLEM
I was able to solve the problem using let thanks to Ben McCormick's comment here:
JavaScript closure inside loops – simple practical example
The solution was to simply use let in the for loop.
for (let i = 0; i < dropDown.length; i++) {
Because when the click function is called, the variable i has the last value it held, dropDown.length, not the loop value at the time addEventListener was called.
What you want is something like this:
for ( i = 0; i < dropDown.length; i++) {
function addListener(n) {
dropDown[n].addEventListener('click', function() {
subNavList[n].classList.toggle('nav-sub__list--active');
});
}
addListener(i);
}
This question already has answers here:
Javascript infamous Loop issue? [duplicate]
(5 answers)
Closed 5 years ago.
I,
I know, there is a lot information about this on SO, but I didn't find the right answer for my situation.
I have this piece of code:
for(var i=0; i < shop.collections.length; i++){
if(!shop.collection[i].active){
var data = {
name: shop.collection[i].name,
visible: true
};
myOwnService.createCollection(data, accessToken, function(err, response, body){
shop.collections[i].serviceId = body.id;
})
}
My problem is that shop is undefined in the myOwnService.createCollection() service. What is the right way to access the shop variable in the callback, so how I can update the object so I can save the result?
shop is in the parent scope of the myOwnService.createCollection function hence it is accessible inside it.
Only issue in above code is i used in callback which will always be the last value as it will be executed after event loop.
Use IIFE wrapper around myOwnService.createCollection so that scope of i could be passed.
for (var i = 0; i < shop.collections.length; i++) {
if (!shop.collection[i].active) {
var data = {
name: shop.collection[i].name,
visible: true
};
(function(i) {
myOwnService.createCollection(data, accessToken, function(err, response, body) {
shop.collections[i].serviceId = body.id;
});
})(i);
}
}
It should be accessible inside where you try to access. If you are tying Chrome debugger to see if it has the value, Chrome debugger sometime mess up and shows it like is not available.
https://bugs.chromium.org/p/v8/issues/detail?id=3491
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a problem with calling a function with a parameter inside a setTimeout function. Basically I'm trying to make a small online game, where I create a queue of commands and then execute them one at a time (each takes some time to show a visualization).
Unfortunately it seems that I cannot pass any variable as a parameter inside the setTimeout(). Although the variable does exist when I call the function it does not exist later when it is executed. The function doesn't keep track of the passed value.
Is there any solution to this? Thanks a lot for any help. Here is a code I use:
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
var timeout = 0;
for (i = 0; i < commands.length; i++) {
console.log(commands[i].childNodes[0]); //variable exists
setTimeout(function() {go(commands[i].childNodes[0]);}, timeout+=400); //Uncaught TypeError: Cannot read property 'childNodes' of undefined
console.log(commands[i].childNodes[0]); //variable still exists
}
}
function go(command) {
//do somethig based on the passed command
}
When your functions are invoked, i is equal to commands.length and commands[i] is undefined.
They are capturing the variable i, not its value.
When they execute, they get out of i the actual value, but so far it has reached commands.length (that is the condition used to break your loop).
You can do something like this to work around it:
setTimeout(function(j) {
go(commands[j].childNodes[0]);
}.bind(null, i), timeout+=400);
Or this:
setTimeout((function(j) {
return function() {
go(commands[j].childNodes[0]);
};
})(i), timeout+=400);
Note also that, as you defined it, i is a global variable.
As mentioned in the comments by #PMV, there's a much easier way in modern JavaScript (if that's an option for you).
Just use a let statement as it follows:
for (let i = 0; i < commands.length; i++) {
// do whatever you want here with i
}
This will ensure that each iteration gets a new variable named i and you can capture it as in the original code.
You need to make a distinct copy of each item. By the time the setTimeout runs the loop has already finished.
var timeout = 0;
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
for (i = 0; i < commands.length; i++) {
go(commands[i]);
}
}
function go(command) {
setTimeout(function() {
console.log(command);
}, timeout += 400);
}
executeCommands();
<ul>
<li class="cmdplace">A</li>
<li class="cmdplace">B</li>
<li class="cmdplace">C</li>
<li class="cmdplace">D</li>
</ul>
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm trying to use a variable value in a callback function, but am unable to get the original value of the variable when written into the function.
for (i=0; i<2; i++) {
if ($.type(picker[i]) == "object") {
var id = picker[i].$root[0].id;
picker[i].on({
set: function() {
alert("#" + id + " .picker__holder");
},
});
// other code..
}
}
This code is supposed to go throught the picker object twice, where the id value is first "datepicker" and on the second run "datepicker--2" but when the callback is called the id is always the second value "datepicker--2" where for the picker[0].on() callback i'd need it to be the first one ("datepicker").
I assume this is because the value of the variable is read when the callback code is executed, and by that time it has the last value.
What would be a reasonable workaround for this?
Best,
Alari
You need to refactor the inner asynch call into a function.
for (i=0; i<2; i++) {
if ($.type(picker[i]) == "object") {
var id = picker[i].$root[0].id;
// other code..
asynhCall(picker, i, id);
}
}
function asynhCall(picker, i, id)
{
picker[i].on({
set: function() {
alert("#" + id + " .picker__holder");
},
});
}
This is because by the time asynch-callback handler was executed, only id will have only the last value would be used (since because of event-loop model).