Consider such loop:
for(var it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
The output is:
=> 2
=> 2
I would like it to be: 0, 1. I see two ways to fix it:
Solution # 1.
This one based on the fact that we can pass data to setTimeout.
for(var it = 0; it < 2; it++)
{
setTimeout(function(data) {
alert(data);
}, 1, it);
}
Solution # 2.
function foo(data)
{
setTimeout(function() {
alert(data);
}, 1);
}
for(var it = 0; it < 2; it++)
{
foo(it);
}
Are there any other alternatives?
Not really anything more than the two ways that you have proposed, but here's another
for(var it = 0; it < 2; it++)
{
(function() {
var m = it;
setTimeout(function() {
alert(m);
}, 1);
})();
}
Essentially, you need to capture the variable value in a closure. This method uses an immediately invoked anonymous function to capture the outer variable value it in a local variable m.
Here's a Working Demo to play with. add /edit to the URL to see the code
With the let keyword you can get around this completely:
for(let it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
Similar to above solution but self invoking inside of setTimeout function
for(var it = 0; it < 2; it++)
{
setTimeout(function(cur) {
return function(){
alert(cur);
};
}(it), 1);
}
Similar to the other solutions, but in my opinion cleaner:
for (var it = 0; it < 2; it++) {
// Capture the value of "it" for closure use
(function(it) {
setTimeout(function() {
alert(it);
}, 1);
// End variable captured code
})(it)
}
This keeps the same variable name for the capture, and does it for the entire loop, separating that from the logic of the timeout setup. If you want to add more logic inside the block, you can trivially do that.
The only thing I don't like about the solution is the repeat of "it" at the end.
Related
I tried to get loop values inside knex function but I got final value of a loop.
for (i = 0; i < 10; i++) {
knex_in.raw(query).then(function (result) {
console.log(i)
});
}
Need help.
The counter in your loop (i) is a global variable. On each iteration of your loop, you are creating a promise. By the time your promises have resolved, the loop is complete, therefore i is the final value.
The solution is to save the value of i in a scoped variable. This can be done in a few ways, here are two:
You can use let (depending on ES6 support)
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 500);
}
Or you could store the value of i in a scoped variable by creating a function:
function someFunction(value) {
setTimeout(function() {
console.log(value);
}, 500);
}
for (i = 0; i < 10; i++) {
someFunction(i)
}
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 7 years ago.
I want to fade in 4 div boxes, one by one.
In css they have the opacity = 0.
Here my JavaScript code:
function fadeIn() {
var box = new Array(
document.getElementById('skill1'),
document.getElementById('skill2'),
document.getElementById('skill3'),
document.getElementById('skill4')
);
var pagePosition = window.pageYOffset;
if (pagePosition >= 1000) {
for (var i = 0; i < box.length; i++) {
setTimeout(function(i) {
box[i].style.opacity = "1";
}, i * 500);
}
}
}
Well, the function has to start if you scroll the page to the position 1000px and called in the body-tag:
Without the setTimeout it works, but with this function the console says:
Uncaught TypeError: Cannot read property 'style' of undefined
I'm a beginner and want to understand JS, so please don't provide an answer using jQuery.
By the time your your timeout runs, the loop has finished processing, so i will always be the last iteration. You need a closure:
for(var i = 0; i < box.length; i++) {
(function(index) {
setTimeout(function() {
box[index].style.opacity = "1";
}, index*500);
})(i)
}
The problem is the scope. When anonymous function executes inside timeout i variable has the last value of the i in the iteration. There are two solutions:
1) Use an IIFE:
for (var i = 0; i < box.length; i++) {
(function (i) {
setTimeout(function (i) {
box[i].style.opacity = "1";
}, i * 500);
})(i);
}
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);//prints out 0 1 2 3 4
}, i * 500)
})(i);
}
2) Using let:
for (let i = 0; i < box.length; i++) {
setTimeout(function (i) {
box[i].style.opacity = "1";
}, i * 500);
}
"use strict";
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 500)
}
The let statement declares a block scope local variable, optionally
initializing it to a value.
Keep in mind let is feature fo ecmaScript 6.
I'm trying to make a few functions to work one after the other with a waiting time of 1.5 seconds between them.
NOW, when i try doing so with the same Id (Inside the "NoteList(>here<)", like 1, 2, 3, or any other, it works;
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[0]) }, i * 1000 + 1000);
}
BUT! when i try doing so with the var i, it doesn't work and gets the all of the functions in the page stuck. any idea why?
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[i]) }, i * 1000 + 1000);
}
That would be because all of the functions refer to the same live i variable, not the value of the variable at the time you called setTimeout(). Which means by the time the timeouts actually run your function i will be 36.
Try this instead:
for (var i = 0; i < 36; i++) {
(function(x){
setTimeout(function () { OnClcRandom(NoteList[x]) }, i * 1000 + 1000);
)(i);
}
This executes an anonymous function on each iteration of the loop, with each execution getting its own x parameter for use in your original function.
Javascript doesn't create local scope for block. :)
And in your second example var i equal 36 (last value).
You need create local scope inside loop.
for (var i = 0; i < 36; i++) {
(function (i) {
setTimeout(.......);
}(i))
}
You also may fixed 'i' value assign it to function property:
for (var i = 0, f; i < 36; i++){
f = function _callback() { var i = _callback.i; .....};
f.i = i;
setTimeout(f, i * 1000);
}
It isn't really a problem for me. I just want to know how I can do it correctly, and not with a workaround. Well, if we use for() and some delayed events, only the last value is considered.
Test: http://jsfiddle.net/39dQV/
// Using only i (does not work)
for(var i=0; i<10; i++) {
setTimeout(function() {
test1.textContent = i;
}, i * 1000);
}
// Private scope to i (does not work either)
for(var i=0; i<10; i++) {
var x = i;
setTimeout(function() {
test2.textContent = x;
}, i * 1000);
}
// Callback scope (workaround)
function set_textContent(i) {
setTimeout(function() {
test3.textContent = i;
}, i * 1000);
};
for(var i=0; i<10; i++) {
set_textContent(i);
}
What do I need do that to so it works correctly, ie: consider the current value of i, instead of the last value changed by time?
Your solution isn't really a "workaround", it's nearby the best way:
for(var i=0; i<10; i++) {
setTimeout(function() {
test1.textContent = i;
}, i * 1000);
}
Can't work! i is in local scope an not longer defined when the function is executed.
for(var i=0; i<10; i++) {
var x = i;
setTimeout(function() {
test2.textContent = x;
}, i * 1000);
}
Same situation here, x is local in the loop.
You need a own scope for your variable. The only way to define such a scope, is to encapsulate the definition of your timeout function inside a closure or function, like you did in your third way.
I'd write it so:
for(var i=0; i<10; i++) {
( function( i ) {
setTimeout(function() {
test1.textContent = i;
}, i * 1000);
} ( i ) };
}
the closure defines its own scope, so the value of i is stored.
For more deeper information see: http://www.javascriptenlightenment.com/ (I love it)
This is the regular for-loop:
for (var i = 0; i < n; i++) { ... }
It is used to iterate over arrays, but also to just repeat some process n times.
I use the above mentioned form, but it repulses me. The header var i = 0; i < n; i++ is plain ugly and has to be rewritten literally every time it is used.
I am writing this question because I came up with an alternative:
repeat(n, function(i) { ... });
Here we use the repeat function which takes two arguments:
1. the number of iterations,
2. a function which body represents the process that is being repeated.
The "code-behind" would be like so:
function repeat(n, f) {
for (var i = 0; i < n; i++) {
f(i);
}
}
(I am aware of the performance implications of having two additional "levels" in the scope chain of the process)
BTW, for those of you who use the jQuery library, the above mentioned functionality can be achieved out-of-the-box via the $.each method like so:
$.each(Array(n), function(i) { ... });
So what do you think? Is this repeat function a valid alternative to the native for loop? What are the down-sides of this alternative (other than performance - I know about that)?
Native:
for (var i = 0; i < 10; i++) {
// do stuff
}
Alternative:
repeat(10, function(i) {
// do stuff
});
You say you want a revolution... Well, you know: ruby did it just before (?)
Number.prototype.times = function(func) {
for(var i = 0; i < Number(this); i++) {
func(i);
}
}
means
(50).times(function(i) {
console.log(i)
})
Anyway, don't fight against C, you'll always lose :-P
it's an interesting thought, but if you dislike the syntax for the loop, you could always do a different type of loop:
var i = arr.length;
while (i--) {
// do stuff
}
the reverse while loop is generally faster than a for loop as well.
To address the issue of not having the break statement as others have mentioned, I would solve it this way:
function repeat(n, f) {
for (var i = 0; i < n; i++) {
if (f(i) === false) return;
}
}
Then returning false from within a loop handler will be equivalent to break.
Another disadvantage is that the context changes. You may want to add the option of proxying a context into the loop handlers:
function repeat(context, n, f) {
if (!f) f = n, f = context, context = window;
for (var i = 0; i < n; i++) {
if (f.call(context, i) === false) return;
}
}
Now, an advantage is that the index is preserved by the function scope, to avoid a common bug:
for (var i = 0; i < 10; i++) {
setTimeout(function () {
alert(i); // Will alert "10" every time
}, 1000);
}
repeat(10, function (i) {
setTimeout(function() {
alert(i); // Will alert "0", "1", "2", ...
}, 1000);
});
It seems pretty valid. I honestly don't think that performance would decrease too much. But there is however one big downside, that is easily fixable: the break statement.
function repeat(n, f) {
for (var i = 0; i < n; i++) {
var tcall=i;
tcall.die=function(){i=n}
f.call(tcall);
}
}
This way you would be able to call this.die() instead of break; which I think would throw an error.
Besides what you have already stated the main downside I see is that a "return" statement will work differently. (Which is often why I end up using "for" over "$.each" many times in my own ventures.)