3

Here is a small snippet of code where I feel the closure function has a weird behavior ...

var arr = [5, 6, 7, 8, 9, 0];
var someFn;

arr.forEach(function(val, idx) {
  if (!someFn) {
    someFn = function() {
      console.log(`B: ${idx} : ${val}`);
    };
  }
  console.log(`A: ${idx} : ${val}`);
  someFn();
});

The final console output is ...

A: 0 : 5
B: 0 : 5
A: 1 : 6
B: 0 : 5
A: 2 : 7
B: 0 : 5
A: 3 : 8
B: 0 : 5
A: 4 : 9
B: 0 : 5
A: 5 : 0
B: 0 : 5

I expect someFn to process the incremental value when the forEach is processing, but it always outputs the first value which is "idx: 0, val: 5".

I dont think this is the right behavior because someFn is creating a closure which encloses the variables idx and val and both those variables are changing in the outer function.

Appreciate if someone can kindly explain this behavior.

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
Praj
  • 49
  • 1
  • 5
  • I think it's because you declared `someFn()` with a current value of `idx` and `val` and once you keep calling it you are not passing new values of them. – Lixus Jul 12 '17 at 14:17
  • Your if condition is messing it up: Use this : var arr = [5,6,7,8,9,0]; var someFn; arr.forEach(function(val, idx) { someFn = function(){ console.log(`B: ${idx} : ${val}`); }; someFn(); }); and it will work. – Fahad Nisar Jul 12 '17 at 14:22
  • 1
    @baao the problem has nothing to do with **template literals**. So I'm voting to reopen this. – ibrahim mahrir Jul 12 '17 at 14:24
  • Of course it has, read the answers to the dupe and think about when the template string is evaluated in above code. – baao Jul 12 '17 at 14:26
  • It's because `someFn` was created on the first call of the anounymous function (callback) passed to `forEach`. Each function call creates it own closure. So for all the iterations `someFn` will have the closure of the first iteration, thus showing only those values. – ibrahim mahrir Jul 12 '17 at 14:27
  • 1
    @baao this is not about template literals. This is about closure behaviors. – Praj Jul 12 '17 at 14:28
  • @baao Na man it is not about that. – Fahad Nisar Jul 12 '17 at 14:28
  • @Praj try removing the if condition. – Fahad Nisar Jul 12 '17 at 14:29
  • @baao I just tried it without any use of template literals, and the problem still occur. And none of the answers in the duplicate link is talking about the problem. – ibrahim mahrir Jul 12 '17 at 14:29
  • @ibrahim mahrir So this is about how forEach works. But I am still not willing to believe what you explained (respectfully disagreeing) because someFn is a function created inside the forEach callback. It has to inherit all the variables of the created closure. – Praj Jul 12 '17 at 14:30
  • ok reopened it @ibrahimmahrir – baao Jul 12 '17 at 14:30
  • @Fahad removing the if condition will create a new someFn and that will fix the issue ... no doubt about that. The above code is a real life scenario where I dont want to recreate the someFn for optimization reasons. Of course, my solution was to use a for loop instead of forEach and that fixed the problem and also create someFn once only. I just want to understand the behavior. – Praj Jul 12 '17 at 14:35

2 Answers2

0

According to this other answer on SO:

a closure is a stack frame which is allocated when a function starts its execution...

So every function call creates its own closure.

What forEach does is that it takes a function (callback) and call it multiple times (passing the element from the array along with its index and the array). So, each iteration of forEach creates a new closure.

You are defining someFn on the first iteration (never get redeclared after that), so the closure it is trapped in is the closure of the first iteration. Thus the only available values are the ones on the first iteration.

The closure is not related to the function itself, it is related to its calls.

Example:

function theFunction(value) {
  return function() {
    return value;
  };
}

var someFn1 = theFunction("Ibrahim");
var someFn2 = theFunction("John");

console.log("someFn1: ", someFn1());
console.log("someFn2: ", someFn2());

In the example, each call to theFunction creates a new closure. someFn1 and someFn2, although they're generated by the same function, but don't have access to the same closure.

The equivalent to theFunction in your code is the anounymous function passed to forEach that gets executed (and thus creating clousures) as many times as there is elements in the array.

ibrahim mahrir
  • 31,174
  • 5
  • 48
  • 73
  • I do understand this. But I believe your answer is correct. Though I think that I understand closures really well, but it is so easy to fall for such traps. Thanks and I voted for you. – Praj Jul 12 '17 at 14:51
0

I expect someFn is creating a closure which encloses the variables idx and val and both those variables are changing in the outer function.

No, they don't change. They are new variables that are instantiated with every call to the outer function. The two variables from the first iteration, over which the closure that was created there did close, keep their values.

To get the expected behaviour, you can use a loop without a function, where you only declare two variables:

var arr = [5, 6, 7, 8, 9, 0];

for (var [idx, val] of arr.entries()) {
//   ^^^^^^^^^^^^^^ global variables
  if (!someFn) {
    var someFn = function() {
      console.log(`B: ${idx} : ${val}`);
    };
  }
  console.log(`A: ${idx} : ${val}`);
  someFn();
}

Though usually that's exactly what we try to prevent :-)

Bergi
  • 630,263
  • 148
  • 957
  • 1,375