2

Method # 1

function transform(ar) {
    var alStr = [];
    for(var i=0; i<ar.length; i++) {

        alStr[i] = (function(v) {
            return (function() {
                return v;
            });
        }(ar[i]));
    }

    return alStr;
}

var a = ["a", 24, { foo: "bar" }];
var b = transform(a);
a[1];
b[1]();

Method # 2

function transform(ar) {
    var alStr = [];
    for(var a in ar) {
        var O = function() {
            return a;
        }
        alStr.push(O);
    }
    return alStr; 
}

var a = ["a", 24, { foo: "bar" }];
var b = transform(a);
a[1];
b[1]();

The above mentioned methods are used to convert an array objects into individual functions which on execution return the specific array object. Want to know why method #1 works and method #2 doesnt.

Zaje
  • 2,161
  • 5
  • 26
  • 39
  • 1
    **Only** new functions introduce new variables .. this question has come up a good bit on SO. Search for "javascript loop last value" or similar. –  Aug 06 '12 at 01:12
  • 1
    see [this answer](http://stackoverflow.com/a/3903130/373378) for a good explanation. – J. Holmes Aug 06 '12 at 01:16
  • 1
    In your non-working code, put the `var a` *before* the `for` loop, and it'll do a better job of reflecting reality. There are no variables that are local to the `for` statement, because in JavaScript, scope is always defined by a `function`. This means *all* the functions being created in the `for` loop are referring to the same `a` variable, which is local to the `transform` function. –  Aug 06 '12 at 01:21
  • 1
    see [this article](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting) for a reference on variable scoping and hoisting in javascript. – J. Holmes Aug 06 '12 at 01:32
  • @32bitkid : Both your links were very helpful, thanks a lot. – Zaje Aug 06 '12 at 01:44
  • Lots of answers, but no one has reminded you not to use for..in with an array unless you have a very good reason to do so (such as a sparse array), take precautions to avoid inherited properties (such as a *hasOwnProperty* test) and don't expect members to returned in any particular order. – RobG Aug 06 '12 at 03:14

2 Answers2

4

In Method #2 there are two problems:

  1. You are returning the key name, a, rather than the array value, ar[a]. That is, rather than return a; you want return ar[a];.

  2. The function will always refer to the last value looped through because it references the same scope object. To create a new scope object you will need a closure, a with block, or a bound function.

With a closure:

for(var a in ar) { 
  var O = (function(val) { 
    return function() { 
      return val; 
     }
  })(ar[a]);
  alStr.push(O); 
}

With a with block:

for(var a in ar) { 
  with({val: ar[a]}) {
    alStr.push(function() { 
      return val; 
     });
  }
} 

With a bound function:

for(var a in ar) { 
  var O = function(x) { return x; };
  alStr.push(O.bind(null, arr[a]));
} 
Peter Olson
  • 121,487
  • 47
  • 188
  • 235
  • Point #1 was a typo from my side, sorry. Still trying to understand the second point. – Zaje Aug 06 '12 at 01:16
  • Even though this answer is correct, `function.bind` ought to be preferred here imo. – David Titarenco Aug 06 '12 at 01:36
  • @DavidTitarenco Good point, I updated my answer and +1 to yours. – Peter Olson Aug 06 '12 at 01:40
  • +1 for the `with` block trick. That is a statement that I think about occasionally, but rarely (never) use. I just may start using it a bit more. – gilly3 Aug 06 '12 at 01:40
  • The "with closure" example is actually *without* a closure. It's the closure in the OP's second example that is the issue, using an immediately invoked function expression breaks the closure. – RobG Aug 06 '12 at 03:07
3

Peter Olson is correct. However, the more modern (correct?) way of doing this is by using function.bind(obj, val). Introduced somewhat recently, function.bind allows you to pass variables by value and in certain contexts. Read more here.

So, you could write something like this:

function transform(ar) {
    var alStr = [];
    var O = function(x) { return x }
    for(var a in ar) {
        alStr.push(O.bind(null, ar[a]));
    }
    return alStr; 
}

var a = ["a", 24, 12345];
var b = transform(a);
console.log(a[2]);
b[2]();

This is a more correct paradigm due to the fact that initiating closures has very clear implications. Using bind, however, tends to be a functional approach to be used specifically when function calling (in particular contexts or with particular stipulations).

Using a with block also has some downsides (there are plenty of questions about it).

Bonus: If you wanted b to also represent subsequent changes to the a array, this solution solves that problem:

function transform(ar) {
    var alStr = [];
    var O = function(x) { return ar[x] }
    for(var a in ar) {
        alStr.push(O.bind(null, a));
    }
    return alStr; 
}

var a = ["a", 24, 12345];
var b = transform(a);
console.log(a[2]);
console.log(b[2]());
console.log("*********");
a[2] = "new value!";
console.log(a[2]);
console.log(b[2]());
David Titarenco
  • 30,718
  • 13
  • 54
  • 108