0

I came across this stackoverflow question about recursively flattening a JS array. Here is the accepted answer:

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push.apply(flat, flatten.apply(this, arguments[i]));
        } else {
            flat.push(arguments[i]);
        }
    }
    return flat;
}

flatten([[1], 2, [3, 4]]); // returns [1, 2, 3, 4]

I'm having trouble understanding how flat.push.apply(...) and flatten.apply(...) work.

I understand that the function will only exit if none of the items in the array are arrays themselves. I also understand that Function.prototype.apply() allows you to call a function using an array of arguments.

What I don't understand is why you are using flat.push... if flat will be set to [] at each function iteration. Also, what significance does setting flat as the this context have?

Can someone help explain how the execution of flat.push.apply(flat, flatten.apply(this, arguments[i])); works?

Community
  • 1
  • 1
Himmel
  • 3,629
  • 6
  • 40
  • 77
  • "*if `flat` will be set to `[]` at each function iteration.*" - it's not iteration, it's recursion. And each call does have its own, local variables. – Bergi Mar 29 '16 at 19:33
  • "*what significance does setting `flat` as the this context have?*" - it's the context for the `push` *method*, which is the array that will be pushed upon. Just like if you regularly invoke `flat.push(…)` *on `flat`*. – Bergi Mar 29 '16 at 19:33
  • `flat` is not set each iteration, it's only set once, before the loop. – dandavis Mar 29 '16 at 19:34
  • Then how does the execution stack work for recursion, and how is it different than the execution stack for iteration? – Himmel Mar 29 '16 at 19:34
  • @Himmel: What do you mean by "execution stack"? Recursion has a *call stack*, iteration (in the sense of imperative repetition, like a `for` loop) doesn't have any. – Bergi Mar 29 '16 at 19:35
  • 1
    why? `r=[];r.push([1]);r;` makes `[[1]]`, whereas `r=[];r.push.apply(r,[1]);r;` makes `[1]` – dandavis Mar 29 '16 at 19:36
  • @Bergi if you nest a function within another function, the new function that is called is added to the call stack and has it's own execution context. I meant to say "call stack" and not "execution stack". – Himmel Mar 29 '16 at 19:37
  • @Himmel: Nesting (in the source code) creates scope closures, which are a different kind again. For a call stack, you just need to call any function. – Bergi Mar 29 '16 at 19:39
  • @dandavis, so the function `flatten` is run a total of 4 times, but flat is only reset to `[]` in each execution context? – Himmel Mar 29 '16 at 19:46

1 Answers1

2

He uses flat.push safely, because push is actually called on different array everytime. Each time flatten executes, it creates new array with different reference stored in flat variable, so each recursion is associated with different flat array.

Setting flat as this context provides the array to be called push upon. Try setting it to null, and you will probably get TypeError because of illegal operation - trying to call push on null.

Now for the more complicated part. push accepts a list of arguments to be added to array, right? But it is sometimes not convenient to supply these arguments one by one, especially, if you don't know, how many of them push should receive, as in your case, where the sizes of arrays to be flatten vary.

That's why the author of that code invokes push via apply with array reference as second argument - which is the returned value of flatten.apply(this, arguments[i]) - reference to array containing only numbers by that time. Each number of that array is pushed to flat, because apply called push and passed whole list of numbers of that array via arguments to push.

The flatten.apply(this, arguments[i]) part is invoked via apply because of similar reasons - it utilizes arguments (array-like object each function has) to process function args easily. Handing over this in the first arg of apply doesn't really matter here, because there is no use for this in flatten (this points to global object in your code sample).

Dan Macak
  • 16,109
  • 3
  • 26
  • 43