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.

6
  • 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. Commented Aug 6, 2012 at 1:12
  • 1
    see this answer for a good explanation. Commented Aug 6, 2012 at 1: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. Commented Aug 6, 2012 at 1:21
  • 1
    see this article for a reference on variable scoping and hoisting in javascript. Commented Aug 6, 2012 at 1:32
  • @32bitkid : Both your links were very helpful, thanks a lot. Commented Aug 6, 2012 at 1:44

2 Answers 2

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]));
} 
Sign up to request clarification or add additional context in comments.

5 Comments

Point #1 was a typo from my side, sorry. Still trying to understand the second point.
Even though this answer is correct, function.bind ought to be preferred here imo.
@DavidTitarenco Good point, I updated my answer and +1 to yours.
+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.
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.
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]());

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.