1

Given this function call:

var funcs = obj.getClosures([2, 4, 6, 8], function(x) {
    return x*x;
});

I have the following function:

getClosures : function(arr, fn) {
    var funcs = [];
    var array = arr;
    var i = 0;
    var l = array.length;

    (function(i, array) {

        for (; i < l; i++) {
            funcs[i] = function(i, array) {
                return fn(array[i]);
            };
        }

    }(i, array));

    return funcs; 

},

I'd like to be able to loop through the returned array and get the square root values of each item in the array exactly like this:

for (var i = 0; i < arr.length; i++) {
   funcs[i]();
}

results each time through loop : 4, 16, 36, 64

Shouldn't my funcs array have a function reference in each index that can be readily invoked with the relevant argument values? Where did I go wrong?

  • I'm suprprised it does something at all. It should throw errors because you don't pass any arguments to the function call ( `funcs[i]()` ). – Felix Kling Jun 29 '13 at 10:51
  • JavaScript will not produce errors if more or less arguments are supplied. Instead it will ignore extra ones or set missing ones to "undefined" – mishik Jun 29 '13 at 10:53
  • @mishik: If your comment was in response to mine, even though you are correct, `array[i]` will throw an error if `array` is `undefined` (which would be the case if no arguments are passed). – Felix Kling Jun 29 '13 at 11:02

2 Answers2

2

There multiple "issues":

The IIFE (function(i, array) { ... }(i, array)); has no benefit at all here. If you remove it the code will have the exact same behavior. If you want to capture the current value of i and array, you would have to move it inside the for loop.

Your function definition is incorrect.

funcs[i] = function(i, array) {
    return fn(array[i]);
};

Inside the function, array[i] will refer to the arguments you pass to the function. If you don't pass any, they will be undefined and the code will throw an error. That is, with that definition, you would have to execute the functions like so:

for (var i = 0; i < arr.length; i++) {
   funcs[i](i, array);
}

which kind of defeats the purpose of generating the functions in the first place.

If you want to create a closure which has access to i and array of the scope where the function was defined in, don't define parameters with the same name.

Possible solution:

for (var i = 0, l = array.length; i < l; i++) {
    (function(i) {
        funcs[i] = function() {
            return fn(array[i]);
        };
    }(i));
}

Or simpler, if your code runs in environments which support .map:

getClosures: function(arr, fn) {
    return arr.map(function(v) {
        return function() {
            fn(v);
        };
    });
},

Related questions:

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • +1; alternatively, move `funcs[i] =` inside the IIFE. This lets you assign more than one handler per IIFE, and is a little more idiomatic IMO. – John Dvorak Jun 29 '13 at 11:16
  • @Jan: I was actually wondering which of the ways to choose. I changed it now, since I think it's a bit closer to the original code and might be easier to understand then. Thanks! – Felix Kling Jun 29 '13 at 11:18
  • Thank you Felix! This was an exercise for me in learning closures, and your points are extremely helpful! The missteps of having the IIFE outside the loop and thinking I had to nest the arguments in each function are now clearer to me. This is getting easier :-) – girls_can_code_too Jul 02 '13 at 23:49
0

Read about Function.prototype.bind:

var obj = {
getClosures : function(arr, fn) {
     var funcs = [];
    var array = arr;
    var i = 0;
    var l = array.length;

    (function(i, array) {

        for (; i < l; i++) {
            funcs[i] = function(i, array) {
                return fn(array[i]);
            }.bind(this,i,array);
        }

    }(i, array));

    return funcs; 

}
}

var funcs = obj.getClosures([2, 4, 6, 8], function(x) {
    return x*x;
});

for (var i = 0; i < funcs.length; i++) {
   console.log(funcs[i]());
}

Outputs:

4
16
36
64

It works since javascript 1.8.5 (firefox 4). I have no idea for other browsers but there is implementation for older versions (should work on older browsers as well)

Ivan
  • 2,262
  • 1
  • 18
  • 16