107

I was toying a bit and was trying to instantiate a new array of length x, where all elements of that array were initialized to a value y:

var arr = new Array(x).fill(y);

This works well if the value of y is anything other than an object. Meaning that if y is an object, the following is true:

var arr = new Array(2).fill({});
arr[0] === arr[1]; //is true;
arr[0].test = 'string';
arr[1].test === 'string'; //is also true;

Is there any way to state that a new object should be created for each element while using the fill-function? Or should I just convert it to a loop?

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Koen Morren
  • 1,233
  • 2
  • 9
  • 12

7 Answers7

106

You can first fill the array with any value (e.g. undefined), and then you will be able to use map:

var arr = new Array(2).fill().map(u => ({}));
var arr = new Array(2).fill().map(Object);
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 29
    also `Array.from({length:2}, u => ({}))` – Slai Feb 01 '18 at 22:15
  • The performance of this way is not the best, unfortunately. I added more explanation to my answer below. – Kostanos Aug 27 '20 at 08:08
  • @Slai very nice answer thanks for that. It took me a while to figure out but it was worth it. – Cooper Mar 29 '21 at 21:56
  • No need for `new` here. This is a good start but other answers are more comprehensive at this point. `Array.from` is just more typing and slower at the present time. – ggorlen Sep 21 '22 at 19:01
47

The accepted answer is good and would work in 90% of cases.

But if you are making high-performance JS application, and if you work with big/huge arrays, Array.map(..) creates big overload in both - memory and processor use, as it creates a copy of an array.

I recommend to use the classic for loop:

    a = new Array(ARRAY_SIZE);
    for (var i = 0; i < ARRAY_SIZE; i++) {
        a[i] = [];
    }
    // or it's one line alternative
    for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);

I tested six alternatives and got this:

  • Array.map(), as proposed above (11x times!!! slower):

     a = new Array(ARRAY_SIZE).fill().map(u => { return []; });
    
  • for loop, the best one (fastest):

     // Standard multi-line way
     a = new Array(ARRAY_SIZE);
     for (var i = 0; i < ARRAY_SIZE; i++) {
         a[i] = [];
     }
    
     // One line syntax
     for (var i = 0, a = []; i < ARRAY_SIZE; a[i++] = []);
    
  • forEach (6x time slower):

     a = new Array(ARRAY_SIZE).fill();
     a.forEach((val, i) => {
         a[i] = [];
     })
    

[UPDATE 2020-08-27] One more way proposed by Ilias Karim below

  • Array.from (30x times!!! slower) - apparently worse in terms of performance, despite the nicest syntax :(

     a = Array.from({ length: ARRAY_SIZE }, () => []);
    
  • [..Array(..)] (5x times!!! slower)

     a = [...Array(ARRAY_SIZE)].map(_=>([]))
    
  • Array.push(..), second place in terms of performance (2x times!!! slower)

     let a = [], total = ARRAY_SIZE;
     while(total--) a.push([]);
    

PS. I used this fiddle for tests.

Kostanos
  • 9,615
  • 4
  • 51
  • 65
  • 4
    This is not a fair nor valid comparison, because you pre-initialize the array and create a PACKED array for the for loop. Your benchmarks are comparing repeated object initialization time with pre-initialized native array iteration. Take away the pre-filling of the array, or change the types of elements to be non-homogeneous, and this benchmark falls apart. – user120242 Jun 21 '20 at 05:02
  • 3
    @user120242 I don't understand your comment? The meaning of this question is about: how to initialize the array with values? I just did a comparison to the accepted answer and discovered the faster way to do it. If you have a better suggestion for array initialization, please fill free to share it with us :) – Kostanos Aug 27 '20 at 07:30
21

One performant solution: Array.from({ length: 5 }, () => new Object())

Ilias Karim
  • 4,798
  • 3
  • 38
  • 60
  • 1
    Can be written shorter: `Array.from({length:5}, _=>{})` – morphles Apr 21 '20 at 06:24
  • check jsperf. microbenchmarks value withstanding, fill and map still outperforms array.from in Chrome as of v80. most likely because PACKED arrays iterate faster – user120242 Jun 21 '20 at 04:41
  • 2
    I just added this way of initialization to my answer above, apparently, it is the worse way in terms of performance. In my tests, this way showed **30x slower** result comparing to the regular **for loop** – Kostanos Aug 27 '20 at 07:59
  • 3
    @morphles it should be `_=>({})` if you want to initialise objects. Otherwise, you'd just fill the array with `undefined`. – VLAZ Nov 26 '20 at 09:21
  • `Array.from({ length: 5 }, Object)` works too, but `[...Array(5)].map(Object)` is shorter and faster. So many answers just say "this is performant" without any evidence. Please be empircal and show your benchmark. – ggorlen Sep 21 '22 at 18:57
4

Shortest Possable:

let node =  [...Array(2)].map(_=>({}))
console.log(node)
Rick
  • 1,035
  • 10
  • 18
  • I tested this one too, and it is 5x times slower of the old fashion **for loop** See my answer above for the performance tests. – Kostanos Aug 27 '20 at 08:06
2

Ilias Karim's answer is most excellent. I just did the following:

a = Array.from({length:l}, () => new Array(c).fill(prefix));

to create a pre-filled 2D array of the specified size, l by c, filled with prefix. Now my code can fill in the slots in the 2D matrix that need non-prefix values.

Sideways S
  • 601
  • 7
  • 12
  • No need for `new` here, and `prefix` must be primitive (unless you want aliasing). – ggorlen Sep 21 '22 at 18:55
  • Wow, four years later... You're correct, but you might notice that the original question and many subsequent answers use "new", so I was just mimicking them at the time. Otherwise, thanks for the correction. – Sideways S Sep 22 '22 at 19:30
0

I wrote a blog post about this: http://www.samhenderson.xyz/posts/12

But the TLDR is that if you want to avoid chaining multiple function e.g. fill, map. And want to avoid writing a loop, then you can use:

const array = Array.from({ length: 2 },()=>({}))

For an array of arrays:

const array = Array.from({ length: 2 },()=>([]))
Sam Henderson
  • 101
  • 10
  • As I mentioned in my answer, this method is the worse in terms of performance... – Kostanos Nov 18 '21 at 21:47
  • `Array.from({length: n}, () => {})` or `Array.from(Array(n), () => {})` is also more characters than `[...Array(n)].map(() => {})` so it seems worth disregarding. – ggorlen Sep 21 '22 at 18:53
0

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);
ggorlen
  • 44,755
  • 7
  • 76
  • 106