1

I am seeking help understanding why the way I am using anonymous functions are erroring in some circumstances.

In the snippet below I popuplate 2 arrays with functions which are invoked at a later stage.

var arr1 = [];
var arr2 = [];

// callback function
var func = function(i){
    return function(){$("body").append(i);}
};

var i = 1;
while(i <= 5)
{
    arr1.push(function(){$("body").append(i);});
    arr2.push(func(i));
  i++;
}

// invoke functions
$("body").append("Output from arr1 == ");
for(var c = 0; c < arr1.length; c++){ arr1[c](); }
$("body").append("<br> Output from arr2 == ");
for(var c = 0; c < arr1.length; c++){ arr2[c](); }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Now, I think I understand why arr1 outputs 66666, because at the time of invocation, i == 6 and becuase it has not been stored as a param within that function at time of creation, i retains it's last known value?

What I really don't understand is why i recieve TypeError: arr2[c] is not a function When I change the callback function to:

var func = function(i){
    $("body").append(i);
};

Why does this happen and is this the most appropriate / elegant way to achieve this functionality.

Zze
  • 18,229
  • 13
  • 85
  • 118
  • because it's not returning a function in the last case. It doesn't return anything at all i.e `undefined` – Abozanona Aug 28 '16 at 23:48
  • check what you're actually pushing into the Array: `var fn = func(i); console.log(fn, typeof fn); arr2.push(fn);` maybe it helps you understand your problem. – Thomas Aug 29 '16 at 00:10

1 Answers1

0

For the first part of the question, you are right, it will retain the latest known value. To avoid that, you need to use closure. For example:

(function(x) {
   arr2.push(func(x));
})(i);

Closure can often be used as a "scope hack". In this case, it will only ensure that the injected value is the constant current value of i.

For the second part of the question, your confusion is probably caused by the fact that you insert the result of the function (i.e.: func(i)) instead of the function itself (func).

In the first scenario, you return an explicit function(){...} so your array will contain a function that you can call with operator "()".

In the second scenario, in which you return $(<...>).append(), it's not guaranteed anymore that your return value is a function. In that case, the result is "jQuery" (see: jQuery append) and not a function, hence the error is correct. You cannot use the value as a function.

One way out is:

arr2.push(func);

Then:

arr2[c](c);

Or probably this:

(function(x){
    arr2[x](x);
})(c);

If needed, it can be helpful to always console.log what is populated inside your arrays before assuming it right. Since Javascript is not a typed language, you may go for a little while before suspecting anything wrong as it will seem work as intended.

Hope this helps!

Frederik.L
  • 5,522
  • 2
  • 29
  • 41
  • Very helpful - thank you! So when I `.push(func(i))` this actually pushes the return value of `func` into the array and not `func` itself ? – Zze Aug 29 '16 at 00:37
  • 1
    Exactly. `func(i)` is actually the return value of `func` given an argument `i`. This can be undefined (nothing), number, string, function, etc. Most of the time it's what you do with the argument that will tell the return type. Javascript will adjust the type according to what it should be. In C or C++, there is no such confusion about types since it is strongly typed. However there is a quite conveniant way to avoid this in JS, with `console.log` which will quickly say if it's Object, Function or constant value. Otherwise, `typeof` will do it. – Frederik.L Aug 29 '16 at 01:40
  • 1
    Downvoter, please express yourself on how to improve! Thank you :) – Frederik.L Aug 29 '16 at 05:17