2

When I tried to run the following piece of code:

function flatten(a) {
  return [].slice.call(arguments).reduce(function(acc, val) {
    return acc.concat(Array.isArray(val) ? flatten.call(null, val) : val);
  }, []);
}

I got the following error:

Uncaught RangeError: Maximum call stack size exceeded

But if I used flatten.apply instead of flatten.call, it works perfectly. (deep flattens the input array).

In addition to this link1 and link2, I read few other blogs and articles to understand why it behaves so. I either couldn't find the answer or I must have overlooked it. (pardon my soar eyes if in latter case)

Of course, the fundamental difference between these 2 is:

apply - requires the optional parameter be an array

call - requires the optional parameters be listed explicitly

Usually, a function may take any type of parameters - primitives and non-primitives. So, my questions are:

  1. Could one of the optional parameters to the call method be of type Array or other non-primitive types?

  2. Why does the above code exceeds call stack, when call is used?

Edit: Since there were 2 call methods used, my description was ambiguous. I made changes to clarify.

Community
  • 1
  • 1
Harish
  • 558
  • 4
  • 11

2 Answers2

7

Your mistake is here:

[].slice.call(arguments).reduce
              ^^^^^^^^^

so when you pass [x], you're calling reduce on [[x]] and the first val becomes [x]. Then you call flatten once again with [x] and the story repeats itself.

On the other side, apply(..., val) will pass just x to flatten, thus reducing the nesting level by one.

If you're interested on how to use apply to flatten a deeply-nested array, this is possible without recursion:

while(ary.some(Array.isArray))
  ary = [].concat.apply([], ary);

Here's a small illustration of call vs apply:

function fun() {
  var len = arguments.length;
  document.write("I've got " 
                 + len 
                 + " " 
                 + (len > 1 ? " arguments" : "argument")
                 + ": " 
                 + JSON.stringify(arguments)
                 + "<br>");   
}

fun.call(null, 123);
// fun.apply(null, 123); <-- error, won't work

fun.call(null, [1,2,3]);
fun.apply(null, [1,2,3]);
                    
                    
georg
  • 195,833
  • 46
  • 263
  • 351
2

When you call flatten.call(null, val), with val being an array, the flatten function receives as argument val what you passed as optional argument to call (the type isn't checked), so arguments is [val].

You see your val array is inside an array. When you call reduce, val, inside the callback, will still be an array, you flattened nothing, that's why it never stops.

When you call flatten.apply(null, val), with val being an array, the flatten function receives as arguments the elements of val, so arguments is identical to val (not [val]) : you've effectively unwrapped the array. That's why it works.

Denys Séguret
  • 335,116
  • 73
  • 720
  • 697