This should not be happening

let two_dimensional_array = [[1],[2]];
let array_clone = [...two_dimensional_array]
array_clone[0][0] = 3;
console.log(two_dimensional_array[0]); // [3]

When I use the spread operator I assume it COPIES the array. Either don't let me spread multidimensional arrays (put an error message) OR clone the array.

The problem is, JS doesn't have multidimensional arrays in the way many other languages do! That's actually an array of arrays, which isn't the same thing as a two-dimensional array. (If you're coming from a C-like language, think of this as "int *two_dim_ar[1]", not "int two_dim_ar[1][1]")

If you need multidimensional array semantics (rectangular shape, full cloning) I recommend making an indexer function, like so:

const array_width = 1;
function indexFor(r, c) {
    return r * array_width + c;
}
let two_dimensional_array =
    [1,
     2];
let array_clone = [...two_dimensional_array]
array_clone[indexFor(0, 0)] = 3;
console.log(two_dimensonal_array[indexFor(0, 0)]); // 3

You can also extend Array to create yourself a multidimensional array class, using something like this:

const array_width = 1;
class TwoDimensionalArray extends Array {
    indexFor(r, c) { return r * array_width + c }
    get(r, c) { return this[this.indexFor(r, c)]; }
    set(r, c, v) { return this[this.indexFor(r, c)] = v; }
    delete(r, c) { delete this[this.indexFor(r, c)]; }
}
let two_dimensional_array = TwoDimensionalArray.of(
    1,
    2
);
// using [...tda] always creates a base Array, but .slice(0) maintains the species:
let array_clone = two_dimensional_array.slice(0); 
array_clone.set(0, 0, 3);
console.log(two_dimensional_array.get(0, 0)); // 3

But of course, you probably want to be able to make arrays of different widths, and to be able to perform various operations on them while maintaining their widths, so make the array class a family of classes instead:

const TwoDimensionalArrayFactory = (array_width) => class extends Array {
    // all code remains the same as above
};
let two_dimensional_array = TwoDimensionalArrayFactory(1).of(
    1,
    2
);
// all remaining code the same

You could obviously do something to translate the array-of-arrays into true multidimensional array form, but if you need that, the exercise is left to the reader :slightly_smiling_face:

One potential pitfall to be aware of: the TwoDimensionalArrayFactory above doesn't cache the dynamically created array classes, so instanceof checks like this will fail:

const tda = TwoDimensionalArrayFactory(1).of(1, 2);
console.log(tda instanceof TwoDimensionalArrayFactory(1)); // false

instead, modify the factory to cache each width, or store the created class at the call site:

const Width1Array = TwoDimensionalArrayFactory(1);
const w1a = Width1Array.of(1, 2);
console.log(w1a instanceof Width1Array); // true
3 Likes

JavaScript also now has structuredClone which can be your alternative to the spread operator here:

let two_dimensional_array = [[1],[2]];
let array_clone = structuredClone(two_dimensional_array);
array_clone[0][0] = 3;
console.log(two_dimensional_array[0]); // [1]
1 Like

That's a good point! I only just recently learned about structuredClone myself so I didn't think of it, but yeah - if you need or want to use the array-of-arrays style for multidimensional arrays, or if you have object members of the array that you also want cloned, structuredClone is a really good option for making a copy. Just be aware that any object members will also be cloned, so:

const apple = {type: "fruit", name: "apple"};
const carrot = {type: "vegetable", name, "carrot"};
const arrayWithObjects = [
  [apple, carrot],
  [carrot, apple],
];
const clonedArray = structuredClone(arrayWithObjects);
console.log(arrayWithObjects[0][0]);           // {type: "fruit", name: "apple"}
console.log(clonedArray[0][0]);                // {type: "fruit", name: "apple"}
console.log(arrayWithObjects[0][0] === apple); // true
console.log(clonedArray[0][0] === apple);      // false
2 Likes