4

While developer a client side application I ran into an error that I believe is related to my incomplete understanding of use of closures. I have trimmed down my code to the following :


var fn1 = function(arr){ 
  return function(val){ 
    var idx;
    var x = 20; 
    for (idx in arr) {
      arr[idx]();
    } 
  } 
}

var fn2 = fn1([function(){
  x+= 20; 
  alert(x);
}])

Now upon executing :

fn2()

I receive an error : ReferenceError: reference to undefined property "x"

I would like to know why this error is arising and why can't fn2 access the variables defined in the local scope of closure function returned by fn1 ?

lorefnon
  • 12,875
  • 6
  • 61
  • 93

2 Answers2

8

In short: closures have access to parent scopes in relation to where they were declared, not where they are used.

The function returned by fn1 has access to the scope of its parent function (fn1 itself), i.e., it has access to arr. But the functions inside your array do not have access to variables defined within fn1 or within the function returned from it, because those functions (inside array) were declared in a different scope.

Regarding the actual problem you're trying to solve with that code, I'm not sure if I understood it correctly. Looks like you're trying to apply some functions to a certain number. I don't understand why that number (20 in your example) is a constant and not a parameter. Here is my proposed solution for that (no closures!):

var applyArrayFunctions = function(arr, x){ 
    for(var i=0; i<arr.length; i++) {
        x = arr[i](x);
    }
    return x;
}

var num = 2;
var fnArray = [
    function(val){
      return val + 20; 
    },
    function(val){
      return val * 2; 
    }
];

console.log(applyArrayFunctions(fnArray, num));​ // logs 44​​​
bfavaretto
  • 71,580
  • 16
  • 111
  • 150
  • 1
    Also explanation needed not to use `fon-in` for arrays and solution to actual question code-wise would be better. – Sarfraz Jun 17 '12 at 18:59
  • Thanks for the carification. Very clearly stated. Removed my misconception. – lorefnon Jun 17 '12 at 19:00
  • @Sarfraz I simply removed the hasOwnProperty check for making the code consize. Thanks for the tip though. – lorefnon Jun 17 '12 at 19:02
  • 1
    @Lorefnon: No i mean you should NOT use `for-in` loop for arrays (it is used for objects), use simple `for` loop eg `for (idx = 0; idx < arr.length; idx++) {...}` – Sarfraz Jun 17 '12 at 19:03
  • I was hoping you could provide a reference (or explain) the problem regarding for-in and arrays ? – lorefnon Jun 17 '12 at 19:07
  • 1
    @Lorefnon: **Simple: Use `for-in` only for objects not arrays.** If you have confusion about the difference between an object and array, you can ask a question to get help on that too. – Sarfraz Jun 17 '12 at 19:13
  • @bfavaretto While your solution does indeed resolve the problem, I was simply curious if there did exist a way to inject the value of a variable into the local scope of a priorly unknown function, without passing a parameter. – lorefnon Jun 17 '12 at 19:15
  • @Sarfraz No my confusion was not regarding difference between object and array.For reference : http://stackoverflow.com/questions/500504/javascript-for-in-with-arrays provides a proper discussion on the problems of using for-in with arrays. – lorefnon Jun 17 '12 at 19:20
  • @Lorefnon: Ok nice to know that, have fun. – Sarfraz Jun 17 '12 at 19:21
  • @Lorefnon So no, there is not way to inject variables into arbitrary scopes like that. Even closures do not work by injecting anything. One more thing: I think what Sarfraz is trying to say is about `for-in` is (quoting [MDN](https://developer.mozilla.org/en/JavaScript/Reference/Statements/for...in)): "iterating over an array may not visit elements in a consistent order" (which is relevant for your example). – bfavaretto Jun 17 '12 at 19:30
  • @Sarfraz is right. Unless OP really wants to use an Array as you would an Object, `for-in` is the wrong tool for iteration. Using `.hasOwnProperty()` is not a good fix. –  Jun 17 '12 at 19:46
3

I suspect the error is in the line x+=20

var x = 20 only exists inside the scope of the anonymous function returned by fn1. x+=20 evaluates before fn1 does, so x doesn't even exist at that time.

Vince Blas
  • 81
  • 1
  • 7