To add to answers that explain the aliasing issue and how to solve it, here's a handy function that can be used to create arrays with cleaner syntax for the caller:
const array = (length, fill) =>
[...Array(length)].map((_, i) =>
typeof fill === "function" ? fill(i) : fill
);
// usage:
const a = array(3, i => array(3, j => [i, j]));
a[0][0][0] = -42;
console.log(a);
Note that you still need to use a callback function for non-primitive values. This is actually a feature as it exposes the index and lets you provide arbitrary logic to fill the element. If you're concerned about accidentally passing a non-primitive, non-function object as the fill value, you can throw an error.
If you really want to be able to pass an object directly and have it copied under the hood, here's an adjustment that pretty much prohibits aliasing:
const array = (length, fill) =>
[...Array(length)].map((x, i) =>
typeof fill === "function" ? fill(i) :
typeof fill === "object" ? _.cloneDeep(fill) : fill
);
// usage:
const a = array(2, array(2, {foo: 3}));
a[0][0].foo = 42;
console.log(a);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
In general, I suggest avoiding .fill()
almost entirely in favor of the spread syntax [...Array()]
because it's easy to forget about the aliasing behavior and wind up with a frustrating bug.
If speed matters, use a traditional for
loop:
const array = (length, fill) => {
const a = [];
for (let i = 0; i < length; i++) {
a[i] = typeof fill === "function" ? fill(i) : fill;
}
return a;
};
// usage:
const a = array(2, () => array(2, Object));
a[0][0].foo = 42;
console.log(a);