Why does [].concat(...arr) help to flatten nested arrays

Viewed 237

I just began learning about javascript a month ago. Was reading up the Array.prototype.flat() MDN to solve a problem, and I couldn't figure out why the solution works the way it work.

They wrote about alternative methods to flatten an array, and used reduce and concat at follows:

const arr0 = [1, 2, [3, 4]];

// To flat single level array
const arr1 = arr0.flat();
console.log(arr1) // [1, 2, 3, 4]

// is equivalent to
const arr2 = arr0.reduce((acc, val) => acc.concat(val), []);
console.log(arr2) // [1, 2, 3, 4]

// or with decomposition syntax
const flattened = arr => [].concat(...arr);

const arr3 = flattened(arr0)
console.log(arr3)

The usual acc.concat(val) makes sense to me. What I don't understand is the decompositional syntax part. From my understanding, when you use .concat(...arr), the spread operator will pass all the values of arr into the .concat method, which means it should be [1, 2, [3,4]]. Why is it that it actually flattened the 1 level deep nested array in the process to return [1,2,3,4]?

Clearly I am understanding something incorrectly. Please enlighten me.

3 Answers

concat function can take number (or other non-array values) as parameter(s) or another array(s) of something and will merge those arguments to 1 array.

Those inputs are same:

[].concat(1,2, [3,4]) is equal to [].concat([1], [2], [3,4]).

Following examples might help you understand:

const test1 = [].concat([3,4])           // [3,4]
const test2 = [].concat(1)               // [1]
const test3 = [].concat(1, 2)            // [1, 2]
const test4 = [].concat(1, 2, [3,4])     // [1,2,3,4]
const test5 = [].concat([1], [2], [3,4]) // [1,2,3,4]
const test6 = [].concat(0, [1,2,[3,4]])  // [0,1,2, [3,4]]
console.log(test6) // it won't flatten deep nested

The usual acc.concat(val) makes sense to me. What I don't understand is the decompositional syntax part. From my understanding, when you use .concat(...arr), the spread operator will pass all the values of arr into the .concat method, which means it should be [1, 2, [3,4]]. Why is it that it actually flattened the 1 level deep nested array in the process to return [1,2,3,4]?

Almost right - it is not [1, 2, [3, 4]] though (that would be an array again) but it would be a tuple of 1, 2, [3, 4].

Writing fn(1, ...[2, 3, 4], 5) is the same as fn(1, 2, 3, 4, 5). So in this case, we are doing [].concat(...[1, 2, [3, 4]]) which is the same as [].concat(1, 2, [3, 4]), so concat is called with three arguments: 1, 2 and [3, 4].

The last puzzle piece is that concat behaves differently for arrays and non-arrays as arguments (see docs):

The concat method creates a new array consisting of the elements in the object on which it is called, followed in order by, for each argument, the elements of that argument (if the argument is an array) or the argument itself (if the argument is not an array). It does not recurse into nested array arguments.

This means that [1, 2].concat(3, [4], 5, [6, 7], 8) produces an array [1, 2, 3, 4, 5, 6, 7, 8] because the arguments 123 and [123] are handled the same. This differentiation between arrays and non-array values in the arguments to concat is what causes the "flattening effect", because it means that [].concat(1, 2, [3, 4]) is valid and returns [1, 2, 3, 4].

Usually, concat is used with arrays, and I myself normally consider x.concat(y, z) as the same as [...x, ...y, ...z] (which wouldn't cause any flattening) but in fact it does support non-array arguments and handles them the same way as if they were the only element in an array - in my example it would be the same as [...x, ...Array.isArray(y) ? y : [y], ...Array.isArray(z) ? z : [z]] which actually is the same as [...x, ...[y, z].flat()]! - I haven't seen non-array arguments to concat used much but it works and that's the key reason why [].concat(...arr) does a one-level-deep flattening!

(By the way, this also applies to the reduce example. It should actually have made equally little sense to you!)

First of all, this will flatten only Array with 1 level sub array. If your array having multiple levels, for example [1,2,[3,[5,[6]]]], this won't flatten the whole array.


In your syntax, array.concat(another_array) concat the array passed as argument to the array calling the concat method. Here, another_array will be added to array. concat will accept multiple arguments.

let a = [1,2,3], b = [4,5,6], c = [7,8,9];

console.log(a.concat(b,c))
console.log(b.concat(c,a));

concat won't alter original array.

concat can also accept individual elements and will add it to the array, just like push.

Example

let a = [1,2,3];
a = a.concat(4,5,6);
console.log(a)

By using the spread syntax ..., you are destructuring the array elements and pass the first 2 as individual elements and next one as an array ( (1,2,[3,4])).

concat will consider this as 3 arguments and concat this to the empty array you're calling the method on.

Related