This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 4 years ago.
How do I bind (or pass a value) to a callback at the time the callback function is passed to the caller?
For instance:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(){
console.log("Iteration number ", i);
}.bind(i), i*1000);
}
}
callbackTest();
Results in:
$ node callback-with-bind.js
Iteration number 3
Iteration number 3
Iteration number 3
I would expect the binding to happen at the time the callback is passed to the caller. But no.
How do I do it?
In my actual application, the caller passes another param to my callback, so I can't simply pass i as a param.
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(myParam){
console.log("Iteration number ", i);
}.bind(i), i*1000);
}
}
callbackTest();
bind, when provided a single parameter, sets the function's this - in your example, the i inside the function is not being bound or altered at all - it's still just using the (global) i. You should use bind's second parameter, which assigns to the function's first argument, and give your function an appropriate first argument so that it can be used:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(internalI){
console.log("Iteration number ", internalI);
}.bind(null, i), i*1000);
}
}
callbackTest();
You could also use no arguments at all and continue using bind(i), but then you would have to replace i with this inside your function, which generally isn't the sort of thing that a this should refer to:
function callbackTest() {
for(i=0; i<3; i++){
setTimeout(function(){
console.log("Iteration number " + this);
}.bind(i), i*1000);
}
}
callbackTest();
Or, you can just declare i with let, which has block scope, not global scope or function scope:
function callbackTest() {
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log("Iteration number ", i);
}, i * 1000);
}
}
callbackTest();
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 1 year ago.
Hello i want to have 4 timeOuts to update balls positions because the balls don't go at the same speed.
here's a part of my code:
var i=1;
while(i<=5)
{
console.log(i);
intervals[i-1]=setInterval(function()
{
MoveBallHorizontalyWithSpeed(i);
MoveBallVerticalyWithSpeed(i);
showBalls();
console.log(i);
},speed/i);
i++;
}
the problem is that each timeout calls the functions with 6 but i whant the first timeout calling MoveBallHorizontalyWithSpeed(1) the second MoveBallHorizontalyWithSpeed(2) etc...
is there a way to do that faster than writing each timeout?
The problem is that you are using a global variable inside the loop so all the setInterval functions are using the same variable i, in this type of scenario always use a local variable. You should change your code like this:
for(let i = 1; i<=5; i++)
{
console.log(i);
setInterval(function()
{
console.log(i);
},2000);
}
Please try this:
var i=1;
while(i<=5)
{
console.log(i);
const j = i;
intervals[i-1]=setInterval(function()
{
MoveBallHorizontalyWithSpeed(j);
MoveBallVerticalyWithSpeed(j);
showBalls();
console.log(j);
},speed/j);
i++;
}
The reason the while loop will finished its execution and will update the value of the i. So when setInterval execute it get the most latest value of i that is 6.
To avoid this you can create a closure and an IIFE and pass the value of i as a parameter to IIFE. Inside IIFE you can call setInterval
var i = 1;
intervals = [];
while (i <= 5) {
intervals[i - 1] = (function(x) {
setInterval(function() {
MoveBallHorizontalyWithSpeed(x);
MoveBallVerticalyWithSpeed(x);
}, 10 / x);
})(i)
i++;
}
function MoveBallVerticalyWithSpeed(speed) {
console.log(speed)
}
function MoveBallHorizontalyWithSpeed(speed) {
console.log(speed)
}
I am trying to pass I into the inputItems[i].on so i can set it to do a specific task (defined by createSelectedInputItem(i)), how do you pass the variable i into the function() { ..operation here }?
for(var i=0; i< 6; i++){
console.log("setting mouse event for : " + i);
// Bring in all the input items
inputItems[i].on('click', function() {
console.log("i is still:" + i );
input.tween.reverse();
console.log("pressed" + i);
createSelectedInputItem(i);
for(var j=0; j< 6; j++){
inputItems[j].tween.reverse();
}
});
}
LOG
//When the page is loaded
setting mouse event for : 0
setting mouse event for : 1
setting mouse event for : 2
setting mouse event for : 3
setting mouse event for : 4
setting mouse event for : 5
//When pressing one of the inputItems
i is still:6
pressed6
this isnt even suppose to exist
The problem you are facing is that of closures, which are a slightly non-intuitive aspect of Javascript's scoping.
Consider how many variables you have there. How many times does the variable i exist? The answer is "as many times as there are scopes that define i". In this case, that means there is only one variable i, which is referred to throughout your code.
You do a loop with i:
for(var i=0; i< 6; i++){
After this loop has completed (which happen before you do any clicks), i is 6. It will never change again, and it will never refer to any other number.
So the click handler fires, and this line of code is run:
console.log("i is still:" + i );
i is the same variable, and it will therefore be the value 6.
The way around this is to introduce a new variable for each iteration of the loop.
for (var i = 0; i < 6; i++) {
(function (innerI) { // create a function with an argument called innerI
console.log("setting mouse event for : " + i);
// Bring in all the input items
inputItems[innerI].on('click', function () {
console.log("i is still:" + i);
console.log("innerI is: " + innerI);
input.tween.reverse();
console.log("pressed" + innerI);
createSelectedInputItem(i);
for (var j = 0; j < 6; j++) {
inputItems[j].tween.reverse();
}
});
}(i)); // invoke the function with i as the argument
}
In this code, we create an anonymous function. The function takes one argument, innerI. We then immediately invoke that function, and pass i as the argument. This creates a new scope and a new variable, so it is not changed when i++ happens at the end of the loop.
inputItems[i].on('click', function() {
// By the time this runs, any variable outside its scope may change,
// which in this case is 'i' which runs in a loop.
});
You should capture the i by wrapping it like this:
(function(i) {
inputItems[i].on('click', function() {
// Now i is an argument to the wrapping closure
console.log(i);
});
})(i);
I am trying to undrstand the code
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
from here http://bonsaiden.github.com/JavaScript-Garden/#function.closures
I understood this method :
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
Can anyone please help me by explaining the first one?
I will try to explain how I understands the first one,
first i is 0,
setTimeout is called,
self calling function "function(e)" is called with i=0,
Im stuck!! what happens when this function returns a function?
All the first one does is return a function that will be called after the timeout happens.
The purpose of it is to create a sub-scope for each iteration of the for loop so that the incrementing i isn't overridden with each iteration.
More explanation:
Lets take this apart into two different pieces:
for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
This is the first piece:
for(var i = 0; i < 10; i++) {
setTimeout(function(){
console.log(i); //9-9
},1000);
}
Now, when you run this loop, you will always get console.log()'s that contain 9 instead of 0 to 9. This is because each setTimeout is using the same reference to i.
If you wrap the setTimeout part of that in an anonymous function, it creates a scope for each iteration allowing each setTimeout to have it's own i value.
for(var i = 0; i < 10; i++) {
setTimeout((function(i) {
return function() {
console.log(i); // 0-9
}
})(i), 1000)
}
The outer function inside the setTimeout gets executed immediately with an i of 0 for first iteration, 1 for second, etc. That function then in turn returns a function which is the function that setTimeout uses. A function is being generated and returned for each iteration of the loop using a different value for i.
Both end up with the same result: a setTimeout is called with a function to invoke, which writes a number from 0 to 9 on the console. Both use nested functions to get the current value of i into a closure so you don't end up logging 10 9's.
The first code chooses to have a function returning the function that setTimeout will call. The second changes the nesting order so that the closed-over function invokes setTimeout itself. The net effect is the same.
Other than stylistic reasons and personal choice, I don't see a reason to choose one over the other.
"Can you please check the updated question specifying where im getting confused"
OK, here's the long explanation. Remember that the first parameter to setTimeout() needs to be a reference to the function that you want executed after the specified delay. The simplest case is to just name a function defined elsewhere:
function someFunc() {
console.log("In someFunc");
}
setTimeout(someFunc, 100);
Note there are no parentheses on someFunc when passing it as a parameter to setTimeout because a reference to the function itself is required. Contrast with:
setTimeout(someFunc(), 100); // won't work for someFunc() as defined above
With parenthese it calls someFunc() and passes its return value to setTimeout. But my definition of someFunc() above doesn't explictly return a value, so it implicitly returns undefined - which is like saying setTimeout(undefined, 100).
But it would work if changed someFunc() to return a function instead of returning undefined:
function someFunc() {
return function() {
console.log("In the function returned from someFunc");
};
}
So now (at last) we come to the code from your question:
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
Instead of referencing a function by name and calling it as someFunc(i) it defines an anonymous function and calls it immediately as (function(e) {})(i). That anonymous function returns another function and it is that returned function that becomes the actual parameter to setTimeout(). When the time is up it is that returned function that will be executed. Because the (inner) function being returned is defined in the scope of the (outer) anonymous function it has access to the e parameter.
I know that this code doesn't work and I also know why.
However, I do not know how to fix it:
JavaScript:
var $ = function(id) { return document.getElementById(id); };
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', function()
{
console.log(i);
});
}
});
HTML:
1
2
3
I want it to print the number of the link you clicked, not just "4".
I will prefer to avoid using the attributes of the node (id or content), but rather fix the loop.
Wrap the loop block in its own anonymous function:
document.addEventListener('DOMContentLoaded', function()
{
for(var i = 1; i <= 3; i++)
{
(function(i) {
$('a' + i).addEventListener('click', function() {
console.log(i);
})
})(i);
}
}
This creates a new instance of i that's local to the inner function on each invocation/iteration. Without this local copy, each function passed to addEventListener (on each iteration) closes over a reference to the same variable, whose value is equal to 4 by the time any of those callbacks execute.
The problem is that the inner function is creating a closure over i. This means, essentially, that the function isn't just remembering the value of i when you set the handler, but rather the variable i itself; it's keeping a live reference to i.
You have to break the closure by passing i to a function, since that will cause a copy of i to be made.
A common way to do this is with an anonymous function that gets immediately executed.
for(var i = 1; i <= 3; i++)
{
$('a' + i).addEventListener('click', (function(localI)
{
return function() { console.log(localI); };
})(i);
}
Since you're already using jQuery, I'll mention that jQuery provides a data function that can be used to simplify code like this:
for(var i = 1; i <= 3; i++)
{
$('a' + i).data("i", i).click(function()
{
console.log($(this).data("i"));
});
}
Here, instead of breaking the closure by passing i to an anonymous function, you're breaking it by passing i into jQuery's data function.
The closure captures a reference to the variable, not a copy, which is why they all result in the last value of the 'i'.
If you want to capture a copy then you will need to wrap it in yet another function.
Running the following code:
for (var i=0; i<3; i++) {
setTimeout( function() { console.log(i); } , 500 );
}
Outputs "3" three times. It's outputting the final value of i as opposed to the value of i when the inner function is created.
If I want the output to be 1, 2, and 3, how would I write this code? How can I get it to use the value of i at the time the function is defined as opposed to its final value?
for (var i=0; i<3; i++) {
setTimeout( function(val) { return function() { console.log(val); } }(i), 500 );
}
So, at setTimeout time (at the time we define the function for setTimeout), we're calling the anonymous function taking val as a parameter. This creates a closure for each function call, storing the value of val within the scope of the function we just called. I used a self-invoking function, which creates an immediate closure.
In the code you provided, the code creates a closure, but for the larger scope of the entirety of the code, so i is local to the whole code, meaning that at run-time, the anonymous function will use the variable i that the rest of the code uses.
function f(i){
return function(){console.log(i);};
}
for (var i=0; i<3; i++) {
setTimeout(
f(i)
, 500 );
}
The modern alternative to an explicit closure (which can get a bit hairy to read when you've got a double-wrapped function) is Function#bind. Once you've hacked in support for browsers that don't do ECMAScript Fifth Edition yet, you can say:
for (var i=0; i<3; i++) {
setTimeout(function(i) { console.log(i); }.bind(window, i), 500);
}
the window is the value that this will be inside the function (you don't need a this here, so we just use the default global object). In the case where you're just calling another function/method, like here with console.log, you can use that to excise the function expression completely:
for (var i=0; i<3; i++) {
setTimeout(console.log.bind(console, i), 500);
}
alternative:
for (var i=0; i<3; i++) {
(function(val){
setTimeout(function() {
console.log(val);
},500)
}(i));
}