why is IIFE needed to create a new scope? - javascript

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

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.

Passing parameters to the function in setTimeout

I want to call a function in a cycle with setTimeout(), and also pass the counter as a parameter. Here's what I tried:
<!DOCTYPE html>
<html><body>
<button onclick="timed1()">Display alerts</button>
<script>
function timed1() {
for (i=1; i<4; i++){
setTimeout(
(function(i){ alert(i) })(i), 1000*i
);
}
}
</script>
</body></html>
Function calls are done with correct values, but immediately instead of the intervals I wanted. I have read this and this SO questions but unfortunately don't see yet what should I change in my case.
You can use let here if you want to bind different values of i to the setTimeout function
function timed1() {
for (let i=1; i<4; i++){
setTimeout(function() {
alert(i)
}, 1000*i)
}
}
timed1()
You're swimming on IIFE (Immediately Invoked Function Expression).
You're calling your function immediately.
setTimeout(
(function(i){ alert(i) })(i), 1000*i
); ^
The scope of your variable i is open, you need to use let keyword.
for (i = 1; i < 4; i++) {
^
Look at this code snippet
function timed1() {
for (let i = 1; i < 4; i++) {
setTimeout(
function() {
alert(i)
}, 1000 * i);
}
}
<button onclick="timed1()">Display alerts</button>
The setTimeout function allows you to pass arguments to the callback function, like so:
function timed1() {
for (i=1; i<4; i++){
setTimeout(alert, 1000 * i, i);
}
}
timed1()
Any arguments after the time are passed along to the callback.
The expression (function(i){ alert(i) })(i) does not return a function for setTimeout to call. If you're going the IIFE way, make sure to return a new function from it:
setTimeout((function(i) {
return function() {
alert(i);
};
})(i), 1000 * i);
With ES2015+ a far better readable equivalent can be realized:
for (let i = 1; i < 4; i++) {
setTimeout(() => alert(i), 1000 * i);
}

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.

JavaScript async callback and scope

Consider the following example:
var cb = function (t) {
console.log('callback -->' + t);
};
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout(function(){
cb(i);
},1000);
}
Working example at jsfiddle
The output of this code snippet is:
0
1
2
callback ---> 3
callback ---> 3
callback ---> 3
Everything works as expected, for loop puts 3 callback calls into the event loop. By the end of the for loop i == 3 and when the callbacks get executed all of them print 3 because they contain the link to the i which is 3.
How could this snippet be improved so when the callback gets executed it uses the actual value which was passed to it.
The output should be:
callback ---> 1
callback ---> 2
callback ---> 3
Thanks in advance.
Create a closure so that the setTimeout handler will refer to the closure's local variable (which in this case, we also named i), not the i from the loop:
for (var i = 0; i < 3; i++) {
(function (i) {
console.log(i);
setTimeout(function () {
cb(i);
}, 1000);
}(i));
}
You could try .bind:
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout(cb.bind(null, i),1000);
}
The demo.
The traditional way to handle this is to create a closure:
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout((function(i){return function(){cb(i)}}(i)),1000);
}
A frequent question.
Let's try to use some features of future of JS. I mean let.
It creates local scope variable and you don't need to use a closure or another trick.
But now it works only in FF (i'm using 20.0.1)
for(var i = 0; i<3; i++) {
console.log(i);
let a = i;
setTimeout(function(){
cb(a);
},1000);
}
I will just use setTimeout() in your cb's functoin
var cb = function (t) {
setTimeout(function(){
console.log('callback -->' + t);
},1000);
};
for(var i = 0; i<3; i++) {
console.log(i);
cb(i);
}

Scope issues with for loop

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)

Categories