Scope issues with for loop - javascript

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)

Related

Using const as loop variable in for loop

I understand the behavior of using var and let in for loop in typescript/javascript but can someone explain why and how a const variable as a loop variable behaves ?
for (const i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 100 * i);
}
From what i understand , when you declare a variable as const and initialize its value , the value cannot be changed
Yet you can see the value being changed in the console.log() .An error has to be thrown while compilation right ?What am i missing here ?
I have created 2 examples for this behavior .
Loop variable as a const
Const variable re assignment
Can someone help me understand this ?
It works in Stackblitz because it is running traspiled code:
AppComponent.prototype.test = function () {
var _loop_1 = function (i) {
setTimeout(function () {
console.log(i);
}, 100 * i);
};
for (var i = 0; i < 5; i++) {
_loop_1(i);
}
};
It won't work if you add a snippet here because it is not transpiled
for (const i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 100 * i);
}
Answering your question,
test(){
for(const i =0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
This code essentially becomes,
test(){
// can be only initialized once
const i;
for(i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
Because every JavaScript variable is hoisted at the top of its scope, in this case the test() as its const variable that's why its hoisted in that block and not accessible outside of it.
To correct the piece of the code:
test(){
// can be only multiple times in that block
for(let i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
Which becomes,
test(){
let i;
// can be only multiple times in that block
for(i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
As both const and let have block scope and is hoisted at the top of the block its defined in, the only difference between const and let is variables declared const cannot be reinitialized.

why is IIFE needed to create a new scope?

From You Don't Know JS:
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
gives
6
6
6
6
6
but using an IIFE like so
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
gives
1
2
3
4
5
My question: why doesn't
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
var j = i;
console.log( j );
}, i*1000 );
}
or
for (var i=1; i<=5; i++) {
function timer() {
var j = i;
console.log(j);
}
setTimeout(timer, i*1000 );
}
work like the IIFE example? It seems to me they both have a function declaration with a new variable j, wouldn't that create a new lexical scope with a specific setting for i?
The important part of the IIFE is that it runs right away; before i changes, it reads its value and puts it in a new variable. The function reading i in your other examples – function timer() – does not run right away, and the value it puts in its new variable is the value of i after it’s already changed.
Also, in ES6, you can just let i = … instead of var i = … and it’ll work fine without the IIFE or j:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
because let has block scope instead of function scope and variables declared in the initialization part of for loops count as being half-inside the for’s block.
i, being declared with var, is hoisted. Variables don't automatically get their scopes bound to an inner function; unless the inner function explicitly has var i or a paramter of i (thus defining a new i bound to the scope of the inner function), i will continue to refer to the hoisted i in the outer scope.
For example, you could do what you were thinking of like this, if you wanted:
for (var i=1; i<=5; i++) {
setTimeout( function timer(i){
console.log( i );
}, i*1000, i );
}
(The third argument to setTimeout is what the function, the second argument, will be called with)
This means that timer will be called with i as it is during iteration, and the function will use a new i, bound to the scope of the function, initialized via the parameter.
It's a pretty bad idea, though - better to use const and let, which have block scope rather than function scope, and better not to shadow outer variables.
This sort of IIFE
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
is often written like
for (var i=1; i<=5; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})(i);
}
so, you can see that "captured" value is i in this case
You can do the same without IIFE
for (var i=1; i<=5; i++) {
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
timer(i);
}
of course, this is equivalent of
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
for (var i=1; i<=5; i++) {
timer(i);
}
if using ES2015+, you can use let
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
Now, if you use a transpiler because you need to support ES5 (or whatever internet exploder supports) you'll see that the transpiled version is
var _loop = function _loop(i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
};
for (var i = 1; i <= 5; i++) {
_loop(i);
}
Which looks incredibly like the previous version of the code

Understanding javascript function scopes by example

What is the difference between below 2 code snippets ?
i could not understand any differences between them.
Both are using closures(as per my knowledge and correct me if i am wrong)
and prints the same output that is 0,1,2,3,4.
for(var i = 0; i < 5; i++) {
(function(){
var tmp = i;
setTimeout(function(){
console.log(tmp);
}, 0)
})();
}
and
for(var i = 0; i < 5; i++) {
setTimeout((function(tmp){
return function() {
console.log(tmp);
}
})(i), 0);
}
If you forget about the identifier resolution done by
var tmp = i;
and rewrite the first snippet to
for(var i = 0; i < 5; i++) {
(function(tmp){
setTimeout(function(){
console.log(tmp);
}, 0)
})(i);
}
then the IIFE here creates a scope and sets delayed execution, while the IIFE in the second snippet creates a scope and returns a function with that scope.

Can't call a function through setTimeout

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);
}

JavaScript variable binding and loop

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.

Categories