Why is a spread element unsuitable for copying multidimensional arrays?
Asked Answered
T

5

6

From mdn: Spread Syntax

Note: Typically the spread operators in ES2015 goes one level deep while copying an array. Therefore, they are unsuitable for copying multidimensional arrays. It's the same case with Object.assign() and Object spread syntax. Look at the example below for a better understanding.

var a = [[1], [2], [3]];
var b = [...a];
b.shift().shift(); // 1
// Now array b is: [[2], [3]]

What is the point of the above statement? The above code sample works just the same as if you'd copied the array in a to b using the .slice() method. I tried adding another dimension to the array here: https://repl.it/HKOq/2 and things still worked as expected.

So why is the spread syntax unsuitable for copying multidimensional arrays?

I'd appreciate any help.

EDIT:

Reading the answers by estus and vol7ron helped me figure things out. Basically, as estus points out technically there are just arrays inside arrays rather than multidimensional arrays.

And as vol7ron explains only the first level of the array is copied so the objects in memory remain the same for any further nested elements.

I was also wrong to suspect that using the spread syntax was supposed to behave any differently than the slice operator

Taub answered 15/4, 2017 at 2:55 Comment(2)
... is not an operator!Claudeclaudel
@FelixKling—please amend the MDN article, which repeats the error multiple times for both spread and rest syntaxes. ... is a punctuator that is used in rest and spread syntaxes.Disinclined
U
5

Arrays are objects, and [...a] creates a shallow copy of a array object.

For the language itself there are no multidimentional arrays - there are another arrays inside an array. It doesn't matter if contains arrays, plain objects, functions or primitives. For primitives, their values will be copied. Otherwise, the references to objects will be copied. This is what

It's the same case with Object.assign() and Object spread operators

part refers to.

And regarding

The above code sample works just the same as if you'd copied the array in a to b using the .slice() method

...it truly does. This is a neater way to write a.slice() or [].concat(a). With a considerable exception. ES6 rest operator (as well as Array.from(a)) works equally for all iterables, not just for arrays.

For a deep copy of an object ES6 offers nothing new, an object (which an array is) should be recursively copied by hand. To address all the concerns it still makes sense to use proven third-party helper functions, such as Lodash cloneDeep.

Undying answered 15/4, 2017 at 3:26 Comment(4)
So you're saying in other languages you can have real multidimensional arrays where it's all 1 array in memory?Taub
I honestly don't remember such language, this wouldn't be very practical to have n-dimensional arrays as a separate language entity at least. If you have experience with other high-level lingos, you probably know the rules already - array copy usually means a shallow copy (unless proven otherwise).Undying
Btw, to figure out how ES6 works Babel and Typescript REPLs are indispensable. Babel output is stricter to specs, and TS output is easier to read; both are helpful.Undying
developer.mozilla.org/en-US/docs/Web/API/structuredClone | let clone = structuredClone(arr);Gnostic
E
16

Man, programmers are really poor at displaying examples that actually show the difference.

var a = [[['a', 'b'], ['c', 'd']], 'e'];
var b = [...a];
b[0][0][0] = 'z';
b[1] = 'x';
console.log('a', a);
console.log('b', b);

This outputs:

a [[["z", "b"], ["c", "d"]], "e"]
b [[["z", "b"], ["c", "d"]], "x"]

Notice something fishy? Both arrays [0][0][0] value was changed. Meaning that the object sitting at [0][0][0] in both arrays are referenced to the same object, and is not a copy. However the [1] values are different meaning that it is indeed a copy.

Shallow copy means the first level is copied, deeper levels are referenced.

Enright answered 21/4, 2017 at 9:41 Comment(2)
Fantastic example man! I understood this already, but it was still helpful to see in code regardless!Brianbriana
This should be accepted as best answer and deserves bounty also.Onomastic
U
5

Arrays are objects, and [...a] creates a shallow copy of a array object.

For the language itself there are no multidimentional arrays - there are another arrays inside an array. It doesn't matter if contains arrays, plain objects, functions or primitives. For primitives, their values will be copied. Otherwise, the references to objects will be copied. This is what

It's the same case with Object.assign() and Object spread operators

part refers to.

And regarding

The above code sample works just the same as if you'd copied the array in a to b using the .slice() method

...it truly does. This is a neater way to write a.slice() or [].concat(a). With a considerable exception. ES6 rest operator (as well as Array.from(a)) works equally for all iterables, not just for arrays.

For a deep copy of an object ES6 offers nothing new, an object (which an array is) should be recursively copied by hand. To address all the concerns it still makes sense to use proven third-party helper functions, such as Lodash cloneDeep.

Undying answered 15/4, 2017 at 3:26 Comment(4)
So you're saying in other languages you can have real multidimensional arrays where it's all 1 array in memory?Taub
I honestly don't remember such language, this wouldn't be very practical to have n-dimensional arrays as a separate language entity at least. If you have experience with other high-level lingos, you probably know the rules already - array copy usually means a shallow copy (unless proven otherwise).Undying
Btw, to figure out how ES6 works Babel and Typescript REPLs are indispensable. Babel output is stricter to specs, and TS output is easier to read; both are helpful.Undying
developer.mozilla.org/en-US/docs/Web/API/structuredClone | let clone = structuredClone(arr);Gnostic
E
1

New arrays are not created for internal array elements (for multi-dimensional array):

// One-dimensional array
var a = [1,2,3];
var b = [...a];

a[0]='a';
console.log('a',a);
console.log('b',b);   
  // expected: b[0] == 1
  // got:      b[0] == 1



// Multi-dimensional array
var a = [[1], [2], [3]];
var b = [...a];

a[0][0]='a';
console.log('a',a);
console.log('b',b);   
  // expected: b[0][0] == 1
  // got:      b[0][0] == 'a'

It works like slice(), so you would have to traverse the array and create new arrays for each dimension. Here's one quick example:

// Multi-dimensional array
var a = [[1], [2], [3]];
var b = (function fn(ar){
 return ar.map(el=>Array.isArray(el)&&fn(el)||el) 
})(a);

a[0][0]='a';
console.log('a',a);
console.log('b',b);   
  // expected: b[0][0] == 1
  // got:      b[0][0] == 1
Erdah answered 15/4, 2017 at 3:20 Comment(0)
N
0

So what the example is trying to convey is that var b = [...a]; will not unroll the inner arrays of a (e.g b = [1,2,3]), but instead, b will be [[1],[2],[3]]. So b.shift() removes and returns the first element of b which is [1], then the second shift() just removes 1 from that returned array. In one word ... only reaches one level down into your spreaded array, e.g. var b =[...a] is equivelent to var b = [a[0], a[1], a[2]], not var b = [ a[0][0], a[1][0], a[2][0] ] in the example

Name answered 15/4, 2017 at 3:4 Comment(0)
A
0

There is this structuredClone() function available with JS Vanilla. please check the doc @ the following address:https://developer.mozilla.org/en-US/docs/Web/API/structuredClone. This is usable for creating a Deep Copy.

Anarthria answered 14/5, 2023 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.