Why does closure behavior not come into play here:
function init(){
for (var i = 1; i <= 2; i++) {
function timer() {
var k=i;
console.log("in timer");
console.log(timer.i);
console.log(k);
}
timer.i = i;
setTimeout(timer, 0);
}
}
<body onload="init()";>
</body>
Why does timer.i not take the last value of i on the outer function init's stack? However, it does take the outer for k.
There are actually two timers. When the interpreter runs across a function declaration inside a loop like that, it will overwrite the previous function name in the outer scope (if such a function exists). This snippet might make it clearer:
for (var i = 1; i <= 2; i++) {
const oldTimer = window.timer;
function timer() {
console.log("in timer");
console.log(timer.i);
}
timer.i = i;
console.log('oldTimer is ' + oldTimer + (oldTimer ? ' with i of ' + oldTimer.i : ''));
console.log('is oldTimer the same as the new timer? ' + (oldTimer === timer));
setTimeout(timer, 1000);
}
console.log('Outside of for loop: timer is ' + typeof timer);
(The timer is still available outside of the for loop - it gets added to the global object.)
Function declaration in EcmaScript 6 are now block-scoped, just like let declarations (with the difference with let that the variable is declared in the outer global/functionnal scope).
Your code is globally equivalent to
for (var i = 1; i <= 2; i++) {
let timer = function() {
console.log("in timer");
console.log(timer.i);
}
timer.i = i;
setTimeout(timer, 1000);
}
This means you have different timer values.
Note that if you had used var instead of let and instead of a function declaration, you would have got twice the same log.
A good read: http://2ality.com/2015/02/es6-scoping.html
Please note that adding properties to a function isn't really a good practice. You should use scope variables for this, for example:
for (var i = 1; i <= 2; i++) {
let timer_i = i;
setTimeout(function timer(){
console.log("in timer");
console.log(timer_i);
}, 1000);
}
which in this simple case could be just written as
for (let i = 1; i <= 2; i++) {
setTimeout(function timer(){
console.log("in timer");
console.log(i);
}, 1000);
}
Related
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.
I wrote a simple javascript code. My for loop iterates a "let" declared variable, i between 0 and 2. A function gets declared within the loop only when i == 2. The function has to return the value of i variable. When I call this function from outside the loop, the function returns the value of i = 2 (which is natural for a block scope variable i. However, when I rewrite the loop code as its non-loop equivalent code-block, the function (still called from outside the block) returns the vale of i = 3. What is going on?
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo()); //returns 2
// loop equivalent
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function() {
return i;
}
i = 3;
}
console.log(printNumTwo()); // returns 3
Your example is bad because your loop is not counting after 2. So If your loop looks like i <= 3:
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
You would get exactly same result as your non-loop example and that's because of closure in javascript but return breaks for loop. Your function is saving reference to that variable from outside scope.
It's because you're actually setting the function to return the value 3 because of the non-loop environment. You should change the loop a little, adding another variable, but first make your function look like this:
printNumTwo = function() {
return num;
}
And in your simulated loop:
i = 2;
num = i;
printNumTwo = function() {
return num;
}
i = 3;
In your non loop based code, printNumTwo is not executed at the same point of its declaration and so the value of i is updated before it is executed so the value 3 is returned.
{
let i = 0;
i = 1;
i = 2;
printNumTwo = function () {
return i;
}
i = 3;
}
console.log(printNumTwo());
but if you run the following code, it should print 2 since it is executed before value if i is set to 3
{
let i = 0;
i = 1;
i = 2;
printNumTwo = (function() {
console.log(i);
})()
i = 3;
}
Note: return in for loop breaks the further execution of the loop, so even if your first code had i <= 3 as its breaking condition, it will return 2.
for (let i = 0; i <= 3; i++) {
if (i === 2) {
printNumTwo = function() {
return i;
};
}
}
console.log(printNumTwo())
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
// when references 'i' in this function, 'i' goes to the global scope.
return i;
};
// set the value 3 for 'i' in the global scope
i = 3;
}
console.log(printNumTwo()); // return 3;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 0
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 0;
try this
"use strict";
var printNumTwo;
for (let i = 0; i < 3; i++) {
let i = 0;
i = 1;
i = 2;
printNumTwo = function (i) {
return i;
}.bind(null, i); // you set the current value as parameter = 2
i = 3; // i = 3 and break loop
}
console.log(printNumTwo()); // return 2;
I appreciate all the answers I got to my question. All pointing to the case of how a function, when called, handles the environments in which it was both called and created. I read this useful explanation in the book "Eloquent JavaScript" and think it would be good to share it,
"A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called."
~ Eloquent_JavaScript/Closure
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.
Is it safe to assume that for time critical applications it is always better to use function declaration or function expressions, instead of inline functions in heavily executed callbacks?
Consider following test program:
var x;
var count3 = function count3() {
x++;
}
var count2 = function () {
x++;
}
function count() {
x++;
}
function execute(cb) {
cb();
}
x = 0;
var a = new Date().getTime();
for (var i = 0; i < 100000000; i++) {
execute(function named() {
x++;
})
}
a = new Date().getTime() - a;
console.log("Named inline function: " + a);
x = 0;
a = new Date().getTime();
for (var i = 0; i < 100000000; i++) {
execute(function () {
x++;
})
}
a = new Date().getTime() - a;
console.log("Anonymous inline function: " + a);
x = 0;
a = new Date().getTime();
for (var i = 0; i < 100000000; i++) {
execute(count);
}
a = new Date().getTime() - a;
console.log("Function declaration: " + a);
x = 0;
a = new Date().getTime();
for (var i = 0; i < 100000000; i++) {
execute(count2);
}
a = new Date().getTime() - a;
console.log("Anonymous function expression:" + a);
x = 0;
a = new Date().getTime();
for (var i = 0; i < 100000000; i++) {
execute(count3);
}
a = new Date().getTime() - a;
console.log("Named function expression:" + a);
This gives following output (in ms):
Named inline function: 2347
Anonymous inline function: 2121
Function declaration: 771
Anonymous function expression:750
Named function expression:752
Function declaration and function expressions are 3 times faster than inline functions on my humble laptop.
Yes, this can be generalized. Technically the function expression in the loop body is re-evaluated to a new function object every loop turn. As your tests confirm, this is noticeable slower (for millions of iterations) than having one "static" function defined outside the loop. Whether the function is named or not does not really matter, it has a very little overhead in introducing another variable in the execution contexts.
However, this is only relevant when the function really is declared inside the iteration, as in your examples. If you instead had a
function executeAll(cb) {
for (var i = 0; i < 100000000; i++) {
cb();
}
}
then there be no difference between
executeAll(function() { x++; });
and
function increase() { x++; }
executeAll(increase);
since the cb argument is the "static" reference to the one function.
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);
}