Closure (let keyword) - Javascript [duplicate] - javascript

This question already has answers here:
Explanation of `let` and block scoping with for loops
(5 answers)
Closed 5 years ago.
function first(){
var items = document.getElementsByTagName("li");
for(var x = 0; x < items.length; x++){
items[x].onclick = function() {
console.log(x);
}
}
}
function second(){
var items = document.getElementsByTagName("li");
for(var x = 0; x < items.length; x++){
(function(val) {
items[val].onclick = function() {
console.log(val);
}
})(x);
}
}
function third(){
var items = document.getElementsByTagName("li");
for(let x = 0; x < items.length; x++){
items[x].onclick = function() {
console.log(x);
}
}
}
There are 4 elements in the list. Outputs of the 3 functions:
first: 4 4 4 4
second: 0 1 2 3
third: 0 1 2 3
I am not able to understand the output from the third function. In the second function, each call to the IIFE creates a new function object and hence, a new val variable. But in the third function, there is a single copy of the variable x, then how is the output: 0 1 2 3
Please correct me if I am wrong.

In the documentation for let from MDN they have an example covering this exact case in the Cleaner Code in inner functions section:
for (let i = 1; i <= 5; i++) {
let item = document.createElement('li');
item.appendChild(document.createTextNode('Item ' + i));
item.onclick = function(ev) {
console.log('Item ' + i + ' is clicked.');
};
list.appendChild(item);
}
The example above works as intended because the five instances of the (anonymous) inner function refer to five different instances of the variable i. Note that it does not work as intended if you replace let with var, since all of the inner functions would then return the same final value of i: 6. Also, we can keep the scope around the loop cleaner by moving the code that creates the new elements into the scope of each loop.
The same thing applies to your case, because you use let each anonymous function refers to a different instance of x. There is a different instance on each iteration of the loop. This happens because let has a block-level scope instead of the global function scope that var has.

From the docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
let allows you to declare variables that are limited in scope to the
block, statement, or expression on which it is used. This is unlike
the var keyword, which defines a variable globally, or locally to an
entire function regardless of block scope.
When it is var it gets hoisted like all variables.
When it is let the scope is the block it is defined in.

This is one of the trickiest examples of let keyword.
The fact that Let binds variables to the block(& in this case for-loop) means that it binds the variable to every iteration of the loop. So, when loop is finished, you have 4 items (items from item[0] to item[3]) listening to click event.
In fact the for-loop in the third function produces the following:
items[0].onclick = function() {
console.log(0);
}
items[1].onclick = function() {
console.log(1);
}
items[2].onclick = function() {
console.log(2);
}
items[3].onclick = function() {
console.log(3);
}
Make sure to read more about Let here in MDN Some other exciting cases could be found there.

Related

I get strange output when i use var keyword in for loop with setTimeOut inside [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 2 years ago.
I have this code.
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
I don't understand the output from this lines of code:
The output in the console is number 6,and it says that is repeated five times.
If i use the let keyword for "i" then i get the output that i expect,
1,2,3,4,5 after one second
Why is that ?
var has scoping issues and that's why let was introduced.
In your for loop you are defining i, but actually it's stuck to the global scope, and after 1 second, the for loop would actually be done, and when the setTimeout callback is fired, i would have already reached 6 and it's read from the global scope.
In a nutshell, because i is stuck to the upper scope of for, each iteration modifies the calue of i and doesn't create another i.
If you change var to let, the issue is resolved.
setTimeout is async hence will execute after the loop is done, that s why you have i=6, put it in a self invoking function to retain the value of i or use let instead of var in your loop
for (var i = 1; i <= 5; i++) {
((i) => setTimeout(function() {
console.log(i);
}, 1000))(i)
}
for (let i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}

How does Javascript ES6 "let" works behind the scene in a for loop iteration definition [duplicate]

This question already has answers here:
Explanation of `let` and block scoping with for loops
(5 answers)
Closed 5 years ago.
The let statement is advocated for usage in for loops as replacement for var declaration of the iteration variable.
By using let the user can forgo the usage of immediate invocation function
to preserve the value of iteration variable for callbacks for instance.
From mdn:
var list = document.getElementById('list');
for (let i = 1; i <= 5; i++) {
let item = document.createElement('li');
item.appendChild(document.createTextNode('Item ' + i));
item.onclick = function(ev) {
console.log('Item ' + i + ' is clicked.');
};
list.appendChild(item);
}
// to achieve the same effect with 'var'
// you have to create a different context
// using a closure to preserve the value
for (var i = 1; i <= 5; i++) {
var item = document.createElement('li');
item.appendChild(document.createTextNode('Item ' + i));
(function(i){
item.onclick = function(ev) {
console.log('Item ' + i + ' is clicked.');
};
})(i);
list.appendChild(item);
}
If I update the iteration variable i at the end of the loop, the for structure will pick it up and use the new value in the comperator logic of the loop and transfer that value to the next execution of the block.
console.log("start");
for (let i = 0; i < 5; i++) {
console.log("in loop", i);
setTimeout(function () {
console.log(i);
}, 0);
i = i + 2;
}
console.log("end");
Results:
start
in loop 0
in loop 3
end
undefined
2
5
The changes in the second iteration, do not propagate to the first iteration timeout callback.
Currently I thing the for loop implementation create a block execution context for each iteration, transfer the iteration variable as param, and specifically extract its value after the execution of the block ends and use it in the for mechanism for the next iteration. But it would be interesting to know if this is true, or not, and how it is implemented.
There is not much to say. Your observation is correct. There is a separate block for each iteration. So, no matter how long your timeout is, you will always see the same result.
At the end of each iteration the value of i will be passed to the next iteration. Since i is not an object, it will not be passed by reference. Thus a modification/incrementation of i in the next iteration won't affect the value of i in the block of the previous iteration.
Just for fun, try using an object instead of a primitive number:
console.log("start");
for (let i = {i:0}; i.i < 5; i.i++) {
console.log("in loop", i.i);
setTimeout(function () {
console.log(i.i);
}, 0);
i.i = i.i + 2;
}
console.log("end");
Results:
start
in loop 0
in loop 3
end
undefined
6
6
As you can see, in that case your i.i variable will actually be passed by reference and thus you can modify it over different iteration blocks.

IIFE and variable assignment in Javascript. Do we really need an IIFE? What is going on? [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
Say I have this function:
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
setTimeout( function() {
console.log( fruits[i]);
}, i * 1000);
}
}
printFruits(["Lemon", "Orange", "Mango"])
So this returns undefined 3 times.
I can see on a high level that since variables are stored not by value but by reference inside the closure... the loop is finishing first and by the time the functions are dequeued from maybe the Event Loop... the variable is already at undefined (fruits.length evaluates to 3 which is too high for this array size). But why does this perform strangely... it prints "apple" 3 times.
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
var someConstant = i;
setTimeout( function() {
console.log( fruits[someConstant]);
}, someConstant * 100);
}
}
printFruits(["mango", "banana", "apple"])
Shouldn't someConstant be changing as well with i? Why does it seem to be 2 always?
Also this works:
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
(function() {
var current = i;
setTimeout( function() {
console.log( fruits[current]);
}, current * 1000);
})();
}
}
Why is the IIFE necessary to fix this problem?
2nd Example
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
var someConstant = i;
setTimeout(function() {
console.log(fruits[someConstant]);
}, someConstant * 1000);
}
}
printFruits(["Lemon", "Orange", "Mango"])
This logs thrice Mango. Because everytime the someConstant variable is created and re-initialised to i. Recollect how for loop works. i-value is increasing here till 4, checks the condition 4<3, and terminates. So the matter inside the loop executes only thrice. So the last value of someConstant defined in the printFruits functional scope is 2. So when the inner function executes someConstant, its equal to 2. So we get each time Mango.
3rd example
function printFruits(fruits) {
for (var i = 0; i < fruits.length; i++) {
(function() {
var current = i;
setTimeout(function() {
console.log(fruits[current]);
}, current * 1000);
})();
}
}
printFruits(["Lemon", "Orange", "Mango"])
Here the beauty of closures happening. Here its a self executing function being executed, immediately. So when i = 1, it invokes immediately. Every function has a different scope now. There is a seperate current value defined for each. So later when it executes, it recollects whats the value of 'current' when it was defined inside its scope.
The only difference between these samples is that the for loop increments i to 3 before stopping, while the last value which is assigned to someConstant is 2. Your "working" code is outputting Mango three times (index 2 of the array) instead of undefined (index 3 of the array). The general behaviour is the same.
Yes, you do need an IIFE, or ES6's let keyword instead of var.
The difference is that someConstant never gets incremented after the last iteration. The for() loop sets i = 3, the test i < fruits.length fails, so the loop stops. As a result, someConstant is still set to 2 from the last iteration of the loop. Then all the callbacks run, so they all log fruits[2], which is Mango.
You need the IIFE to get each iteration to save its value of i in the closure.

Behaviors of Javascript closures and global functions

While learning about javascript closures, I came across this answer https://stackoverflow.com/a/111111/3886155 on stackoverflow.
It's a very good explanation of closures.
But I have some confusion in Example 4 and Example 5.
I just copy entire snippet here:
Example 4:
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 666;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 667
gSetNumber(5);
gLogNumber(); // 5
var oldLog = gLogNumber;
setupSomeGlobals();
gLogNumber(); // 666
oldLog() // 5
After reading some examples I can say that whenever function inside the function executes it can always remember the variables declared inside outer function.
I agree that if these closure variable updated anyway it still refers to the new variable value.
My problem in this example is specially related to var oldLog=gLogNumber;
How it can return old number after call to the setupSomeGlobals();?
because now the var num has reset.So why it is not using this new num value 666?
Now Example 5:
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + i;
result.push( function() {console.log(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
Here they have pushed functions into array and executed them after loop finishes.But now reference to the closure variables is the latest one after loop finishes.Why not old one?
In both examples you are just assigning function definition to variable or array index.But first one points to old and second one points to latest.Why?
How it can return old number after call to the setupSomeGlobals()
num is not globally scoped, so the value num is in the context of which reference of gLogNumber is invoked.
After invocation of setupSomeGlobals method again, reference to gLogNumber got changed. try this
console.log(Object.is( gLogNumber, oldLog )); //true
setupSomeGlobals();
console.log(Object.is( gLogNumber, oldLog )); //false
So, oldLog retained old reference and hence old value of num, but gLogNumber got new num.
But now reference to the closure variables is the latest one after
loop finishes.Why not old one?
For this problem, have a look at
JavaScript closure inside loops – simple practical example.

Why is result different (using var vs. let)?

This uses var
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 10
This uses let
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i);
};
}
a[6](); // 6
I don't understand why the result is different. Can somebody guide me?
The resulting array consists of functions, each function body looks like this:
console.log(i);
The value of i depends on whether we used var or let to declare the variable.
var (ECMAScript 5 and 6)
Here i is a global variable whose value is 10 after exiting the loop. This is the value that is logged.
let (ECMAScript 6)
Here i is a local variable whose scope is restricted to the for statement. Moreover, this variable gets a fresh binding on each iteration. This is best explained by your code transpiled to ECMAScript 5:
"use strict";
var a = [];
var _loop = function(i) {
a[i] = function() {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
a[6](); // 6
So, on seventh iteration for example, the value of i will be 6 (counting from zero). The function created inside the iteration will refer to this value.
I think it would be much better to not define functions in a loop, you could easily accomplish this with one function definition that returns a closure:
function logNumber(num) {
return function() {
console.log(num);
}
}
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = logNumber(i);
}
a[6]();
Regarding the difference between the two examples, one is using let for block scoping. A better example that shows the difference would be:
ECMA5:
for (var i = 0; i < 10; i++) { }
console.log(i); // 10
ECMA6:
for (let i = 0; i < 10; i++) { }
console.log(i); // i is not defined
Edit: as I stated in my comment to your question, this is more likely a side-effect of the transpiler you are using. Firefox supports block scoping and both versions of your loop produce 10 as output (which they should).
This is correct behavior according to the spec. The behavior with var and let is defined to be different.
See the spec, at https://people.mozilla.org/~jorendorff/es6-draft.html#sec-forbodyevaluation. According to this, the relevant concepts, which make the function declared inside the loop close over the current value of the block-scoped loop index, are things called "per-iteration bindings" and "per-iteration environment".
Babel handles it correctly, producing the following code:
var a = [];
var _loop = function (i) {
a[i] = function () {
console.log(i);
};
};
for (var i = 0; i < 10; i++) {
_loop(i);
}
This implements the semantics of for (let by isolating the contents of the for loop into a separate function parameterized by the index. By virtue of doing that, the function no longer closes over the for loop index, and i is treated separately in each function created. Thus the answer is 6.
Traceur does not produce the correct result. It yields 10.
So the famous question that has been asked 100 times on SO, about why my function declared in a loop and closing over the index index is using the "wrong" value of the loop index, shall be asked no more?
The issue is a bit more nuanced that merely proclaiming that "of course, let is block-scoped". We know that. We get how it works in an if block, for example. But what's going on here is a bit of an twist on block scoping in the context of a for, hitherto unknown to many people including me. It's a variable actually declared outside the "block" (if you think of the block as the body of the for statement) but has a separate existence inside each iteration of the loop.
For more, see https://github.com/babel/babel/issues/1078.
Why is result different in ES6 and ES5?
Because let and var are different. let is block-scoped while var is function-scoped.
In your first example there is only a single variable i. Every function you create has a reference to the same variable i. At the moment you call a[6](), i has the value 10, because that was the termination condition for the loop.
In the second example, every iteration of the loop has it's own variable i. It works exactly like in other languages with block scope.

Categories