Michael Martin-Smucker
Michael Martin-Smucker

Reputation: 12705

How to initialize an array's length in JavaScript?

Most of the tutorials that I've read on arrays in JavaScript (including w3schools and devguru) suggest that you can initialize an array with a certain length by passing an integer to the Array constructor using the var test = new Array(4); syntax.

After using this syntax liberally in my js files, I ran one of the files through jsLint, and it freaked out:

Error: Problem at line 1 character 22: Expected ')' and instead saw '4'.
var test = new Array(4);
Problem at line 1 character 23: Expected ';' and instead saw ')'.
var test = new Array(4);
Problem at line 1 character 23: Expected an identifier and instead saw ')'.

After reading through jsLint's explanation of its behavior, it looks like jsLint doesn't really like the new Array() syntax, and instead prefers [] when declaring arrays.

So I have a couple questions:

First, why? Am I running any risk by using the new Array() syntax instead? Are there browser incompatibilities that I should be aware of?

And second, if I switch to the square bracket syntax, is there any way to declare an array and set its length all on one line, or do I have to do something like this:

var test = [];
test.length = 4;

Upvotes: 962

Views: 1493800

Answers (20)

Gustav Streicher
Gustav Streicher

Reputation: 160

Defining JavaScript Arrays and Array Entries

1. JavaScript Array Entries

JavaScript pretty much has 2 types of array entries, namely mappable and unmappable.

Both entry types are iterable, meaning they both work with the for-of loop.

2. JavaScript Arrays

A JavaScript array can contain both mappable and unmappable array entries.

To prove this I present the following code sample:

const a = (Array(1)).concat([...Array(1)]).concat(['literal']).concat(Array(1));

console.log('a:', a.length, a);
a: 4 [ <1 empty item>, undefined, 'literal', <1 empty item> ]

The same mostly works with the .push() method:

const b = (Array(1));

b.push(...[...Array(1)]);
b.push(['literal']);
b.push(...Array(1));


console.log('b:', b.length, b);
b: 4 [ <1 empty item>, undefined, 'literal', undefined ]

It also mostly works with the .unshift() method:

const c = (Array(1));

c.unshift(...[...Array(1)]);
c.unshift(['literal']);
c.unshift(...Array(1));


console.log('c:', c.length, c);
c: 4 [ undefined, 'literal', undefined, <1 empty item> ]

NOTE that spreading (...) an unmappable array produces a tuple of undefined entries, which is why I've said that the above mostly works the same as with the .concat() method.


Defining arrays initialized with unmappable array entries:

Empty-Item-Filled Arrays ( Length = Given ):

const myArray = Array(LENGTH);

//------------OR------------//

const myArray = new Array(LENGTH);


console.log('myArray:', myArray.length, myArray);
myArray: LENGTH [ <LENGTH empty items> ]

Empty-Item here refers to the inherently unmappable type of entry that the Array() builtin fills arrays with.

NOTE that Array() and new Array() do the exact same thing in JavaScript. So, there really isn't a good reason to use the new keyword with the Array builtin.


Defining arrays initialized with mappable array entries:

1. Undefined-Filled Arrays ( Length = Given ):

const myArray = Array(LENGTH).fill();

//------------OR------------//

const myArray = Array.from({ length: LENGTH });

//------------OR------------//

const myArray = [...Array(LENGTH)];

//------------OR------------//

const myArray = [...(new Array(LENGTH))];


console.log('myArray: ', myArray.length, myArray);
myArray: LENGTH [ undefined, undefined, ___ ]

The new Array() version is just mentioned here for completeness sake.

2. Surrogate-Filled Arrays ( Length = Given ):

const myArray = Array(LENGTH).fill(SURROGATE_VALUE);

console.log('myArray:', myArray.length, myArray);
myArray: LENGTH [ SURROGATE_VALUE , SURROGATE_VALUE, ___ ]

The SURROGATE_VALUE is often chosen as 0 for mathematical use, but it can really be anything.

WARNING: As noted in earlier asnwers, the .fill() method doesn't copy the result passed to it. So, reference data types, like Object and Array, should NOT be used as SURROGATE_VALUEs.

If you need a deeply nested array, initialized with reference data types, like Object and Array, as entries, then it's best to start with a mappable array initialized with undefined entries and to .map() each undefined entry to a new instance of the needed reference data type.

3. Meaningfully-Filled Arrays ( Lenght = Implicit ):

const myArray = [SOME_VALUE_A, SOME_VALUE_B, ___];

console.log('myArray:', myArray.length, myArray);
myArray: IMPLICIT_LENGTH [ SOME_VALUE_A, SOME_VALUE_B, ___ ]

Here the length of the array is implicit since it is equal to the number of meaningful entries you've initialized it with.


Defining Empty Arrays ( Lenght = 0 ):

const myArray = [];

//------------OR------------//

const myArray = Array();

//------------OR------------//

const myArray = new Array();


console.log('myArray: ', myArray.length, myArray);
myArray: 0 []

These arrays contain neither mappable nor unmappable entries since they are empty. So, they are classified separately to make it clear that it doesn't matter how you initialize an empty array.


Mapping Unmappable and Mappable Entries:

Unmappable entries stay the same, whilst mappable entries are mapped.

const a = (Array(1)).concat([...Array(1)]).concat(['literal']).concat(Array(1));

const m = a.map(() => true);


console.log('a:', a.length, a);
console.log('m:', m.length, m);
a: 4 [ <1 empty item>, undefined, 'literal', <1 empty item> ]
m: 4 [ <1 empty item>, true, true, <1 empty item> ]

As you can see, the empty-item or unmappable entries stay the same, whilst the mappable entries, which in the example include an entry of undefined and a string literal, are both mapped to the desired value.


Iterating over Unmappable and Mappable Entries:

const a = (Array(1)).concat([...Array(1)]).concat(['literal']).concat(Array(1));

for (let i = 0; i < a.length; i++) {
  console.log(i, a[i]);
}
0 undefined
1 undefined
2 literal
3 undefined

Similar to the spread operator (...) and with the .push() and .unshift() methods, empty-item entries take on the value of undefined when accessed via the array bracket accessor myArray[INDEX] notation.

The same holds true for for-of loops:

const a = (Array(1)).concat([...Array(1)]).concat(['literal']).concat(Array(1));

for (const entry of a) {
  console.log(entry);
}
undefined
undefined
literal
undefined

Upvotes: 3

Haseeb Anwar
Haseeb Anwar

Reputation: 2918

The simplest form is to use

Array.from({ length: 3 });

// gives you
[undefined, undefined, undefined]

Unlike Array(3) which will give you an array you can't iterate over. Array.from({ length }) gives you an array you can iterate easily.

Array.from({ length: 3 }).map((e, idx) => `hi ${idx}`);
// ['hi 1', 'hi 2', 'hi 3']

Upvotes: 31

Amir
Amir

Reputation: 1348

In addition to the answers of others, another clever way is to use Float32Array to create an array and iterate on it.

For this purpose, create an instance from Float32Array with your desired length like this:

new Float32Array(5)

This code returns an array-like that you can convert it to an array with Array.from():

Array.from(new Float32Array(5)) // [0, 0, 0, 0, 0]

You can also use fill() to change the value of items:

Array.from(new Float32Array(5).fill(2)) // [2, 2, 2, 2, 2]

And of course you can iterate on it:

Array.from(new Float32Array(5)).map(item => /* ... */ )

Upvotes: 3

Domi
Domi

Reputation: 24508

Sparse arrays are here! 🥳 [2021]

In modern JS engines, sparse arrays are fully supported. You can use [] or new Array(len) in any way you like, even with random access. Dictionary mode seems to be a thing of the past.

In current Chrome (and I guess any V8 environment), Arrays can have a length of up to 2^32-1 and allocation is sparse (meaning empty chunks don't use up any memory):

enter image description here

enter image description here

However, there is a catch

On the one hand, for loops work as intended, however, Array's builtin higher order functions (such as map, filter, find, some etc.) ignore unassigned elements. They require fill (or some other method of population) first:

const a = new Array(10);
const b = new Array(10).fill(0);

a.forEach(x => console.log(x)); // does nothing
b.forEach(x => console.log(x)); // works as intended

Old Version

(I removed most of the old version.) The gist was that creating a large array using new Array(largeNumber) or random accessing an array in places that have not yet been allocated would tumble it into "dictionary mode". Meaning you are using an array with indexes, but under the hood it would use a dictionary to store the values, thus messing with performance, and also with iteration behavior. Luckily that is a thing of the past.

Upvotes: 64

Vlad
Vlad

Reputation: 4176

[...Array(6)].map(x => 0);
// [0, 0, 0, 0, 0, 0]

OR

Array(6).fill(0);
// [0, 0, 0, 0, 0, 0]

Note: you can't loop empty slots i.e. Array(4).forEach(() => …)


OR

( typescript safe )

Array(6).fill(null).map((_, i) => i);
// [0, 1, 2, 3, 4, 5]

OR

Classic method using a function ( works in any browser )

function NewArray(size) {
    var x = [];
    for (var i = 0; i < size; ++i) {
        x[i] = i;
    }
    return x;
}

var a = NewArray(10);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Creating nested arrays

When creating a 2D array with the fill intuitively should create new instances. But what actually going to happen is the same array will be stored as a reference.

var a = Array(3).fill([6]);
// [  [6], [6], [6]  ]

a[0].push(9);
// [  [6, 9], [6, 9], [6, 9]  ]

Solution

var a = [...Array(3)].map(x => []);

a[0].push(4, 2);
// [  [4, 2], [], []  ]

So a 3x2 Array will look something like this:

[...Array(3)].map(x => Array(2).fill(0));
// [  [0, 0], [0, 0], [0, 0]  ]

N-dimensional array

function NArray(...dimensions) {
    var index = 0;
    function NArrayRec(dims) {
        var first = dims[0], next = dims.slice().splice(1); 
        if(dims.length > 1) 
            return Array(dims[0]).fill(null).map((x, i) => NArrayRec(next ));
        return Array(dims[0]).fill(null).map((x, i) => (index++));
    }
    return NArrayRec(dimensions);
}

var arr = NArray(3, 2, 4);
// [   [  [ 0,  1,  2,  3 ] , [  4,  5,  6,  7]  ],
//     [  [ 8,  9,  10, 11] , [ 12, 13, 14, 15]  ],
//     [  [ 16, 17, 18, 19] , [ 20, 21, 22, 23]  ]   ]

Initialize a chessboard

var Chessboard = [...Array(8)].map((x, j) => {
    return Array(8).fill(null).map((y, i) => {
        return `${String.fromCharCode(65 + i)}${8 - j}`;
    });
});

// [ [A8, B8, C8, D8, E8, F8, G8, H8],
//   [A7, B7, C7, D7, E7, F7, G7, H7],
//   [A6, B6, C6, D6, E6, F6, G6, H6],
//   [A5, B5, C5, D5, E5, F5, G5, H5],
//   [A4, B4, C4, D4, E4, F4, G4, H4],
//   [A3, B3, C3, D3, E3, F3, G3, H3],
//   [A2, B2, C2, D2, E2, F2, G2, H2],
//   [A1, B1, C1, D1, E1, F1, G1, H1] ]

Math filled values

handy little method overload when working with math


function NewArray( size , method, linear )
{
    method = method || ( i => i ); 
    linear = linear || false;
    var x = [];
    for( var i = 0; i < size; ++i )
        x[ i ] = method( linear ? i / (size-1) : i );
    return x;
}

NewArray( 4 ); 
// [ 0, 1, 2, 3 ]

NewArray( 4, Math.sin ); 
// [ 0, 0.841, 0.909, 0.141 ]

NewArray( 4, Math.sin, true );
// [ 0, 0.327, 0.618, 0.841 ]

var pow2 = ( x ) => x * x;

NewArray( 4, pow2 ); 
// [ 0, 1, 4, 9 ]

NewArray( 4, pow2, true ); 
// [ 0, 0.111, 0.444, 1 ]

Upvotes: 394

Michael Mammoliti
Michael Mammoliti

Reputation: 1597

The shortest:

let arr = [...Array(10)];
console.log(arr);

Upvotes: 100

Wilt
Wilt

Reputation: 44316

In most answers it is recommended to fill the array because otherwise "you can't iterate over it", but this is not true. You can iterate an empty array, just not with forEach. While loops, for of loops and for i loops work fine.

const count = Array(5);

Does not work.

console.log('---for each loop:---');
count.forEach((empty, index) => {
    console.log(`counting ${index}`);
});

These work:

console.log('---for of loop:---');
for (let [index, empty] of count.entries()) {
  console.log(`counting for of loop ${index}`);
}

console.log('---for i loop:---');
for (let i = 0, il = count.length; i < il; ++i) {
  console.log(`counting for i loop ${i}`);
}

console.log('---while loop:---');
let index = 0;
while (index < count.length) { 
  console.log(`counting while loop ${index}`); 
  index++; 
}

Check this fiddle with the above examples.

Also angulars *ngFor works fine with an empty array:

<li *ngFor="let empty of count; let i = index" [ngClass]="
  <span>Counting with *ngFor {{i}}</span>
</li>

Upvotes: 1

mohan babu
mohan babu

Reputation: 1448

Assuming that Array's length is constant. In Javascript, This is what we do:

const intialArray = new Array(specify the value);

Upvotes: 4

wils
wils

Reputation: 791

Please people don't give up your old habits just yet. There is a large difference in speed between allocating memory once then working with the entries in that array (as of old), and allocating it many times as an array grows (which is inevitably what the system does under the hood with other suggested methods).

None of this matters of course, until you want to do something cool with larger arrays. Then it does.

Seeing as there still seems to be no option in JS at the moment to set the initial capacity of an array, I use the following...

var newArrayWithSize = function(size) {
  this.standard = this.standard||[];
  for (var add = size-this.standard.length; add>0; add--) {
   this.standard.push(undefined);// or whatever
  }
  return this.standard.slice(0,size);
}

There are tradeoffs involved:

  • This method takes as long as the others for the first call to the function, but very little time for later calls (unless asking for a bigger array).
  • The standard array does permanently reserve as much space as the largest array you have asked for.

But if it fits with what you're doing there can be a payoff. Informal timing puts

for (var n=10000;n>0;n--) {var b = newArrayWithSize(10000);b[0]=0;}

at pretty speedy (about 50ms for the 10000 given that with n=1000000 it took about 5 seconds), and

for (var n=10000;n>0;n--) {
  var b = [];for (var add=10000;add>0;add--) {
    b.push(undefined);
  }
}

at well over a minute (about 90 sec for the 10000 on the same chrome console, or about 2000 times slower). That won't just be the allocation, but also the 10000 pushes, for loop, etc..

Upvotes: 9

AP.
AP.

Reputation: 8911

With ES2015 .fill() you can now simply do:

// `n` is the size you want to initialize your array
// `0` is what the array will be filled with (can be any other value)
Array(n).fill(0)

Which is a lot more concise than Array.apply(0, new Array(n)).map(i => value)

It is possible to drop the 0 in .fill() and run without arguments, which will fill the array with undefined. (However, this will fail in Typescript)

Upvotes: 610

Alexander Shutau
Alexander Shutau

Reputation: 2800

ES6 introduces Array.from which lets you create an Array from any "array-like" or iterables objects:

Array.from({length: 10}, (x, i) => i);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In this case {length: 10} represents the minimal definition of an "array-like" object: an empty object with just a length property defined.

Array.from allows for a second argument to map over the resulting array.

Upvotes: 68

Ruben Stolk
Ruben Stolk

Reputation: 13294

  • Array(5) gives you an array with length 5 but no values, hence you can't iterate over it.

  • Array.apply(null, Array(5)).map(function () {}) gives you an array with length 5 and undefined as values, now it can be iterated over.

  • Array.apply(null, Array(5)).map(function (x, i) { return i; }) gives you an array with length 5 and values 0,1,2,3,4.

  • Array(5).forEach(alert) does nothing, Array.apply(null, Array(5)).forEach(alert) gives you 5 alerts

  • ES6 gives us Array.from so now you can also use Array.from(Array(5)).forEach(alert)

  • If you want to initialize with a certain value, these are good to knows...
    Array.from('abcde'), Array.from('x'.repeat(5))
    or Array.from({length: 5}, (v, i) => i) // gives [0, 1, 2, 3, 4]

Upvotes: 1183

ajoposor
ajoposor

Reputation: 17

You can set the array length by using array.length = youValue

So it would be

var myArray = [];
myArray.length = yourValue;

Upvotes: -2

zangw
zangw

Reputation: 48346

Here is another solution

var arr = Array.apply( null, { length: 4 } );
arr;  // [undefined, undefined, undefined, undefined] (in Chrome)
arr.length; // 4

The first argument of apply() is a this object binding, which we don't care about here, so we set it to null.

Array.apply(..) is calling the Array(..) function and spreading out the { length: 3 } object value as its arguments.

Upvotes: 11

Felix Kling
Felix Kling

Reputation: 816232

  1. Why do you want to initialize the length? Theoretically there is no need for this. It can even result in confusing behavior, because all tests that use the length to find out whether an array is empty or not will report that the array is not empty.
    Some tests show that setting the initial length of large arrays can be more efficient if the array is filled afterwards, but the performance gain (if any) seem to differ from browser to browser.

  2. jsLint does not like new Array() because the constructer is ambiguous.

    new Array(4);
    

    creates an empty array of length 4. But

    new Array('4');
    

    creates an array containing the value '4'.

Regarding your comment: In JS you don't need to initialize the length of the array. It grows dynamically. You can just store the length in some variable, e.g.

var data = [];
var length = 5; // user defined length

for(var i = 0; i < length; i++) {
    data.push(createSomeObject());
}

Upvotes: 500

user1067305
user1067305

Reputation: 3481

var arr=[];
arr[5]=0;
alert("length="+arr.length); // gives 6

Upvotes: 8

christang
christang

Reputation: 533

I'm surprised there hasn't been a functional solution suggested that allows you to set the length in one line. The following is based on UnderscoreJS:

var test = _.map(_.range(4), function () { return undefined; });
console.log(test.length);

For reasons mentioned above, I'd avoid doing this unless I wanted to initialize the array to a specific value. It's interesting to note there are other libraries that implement range including Lo-dash and Lazy, which may have different performance characteristics.

Upvotes: 15

j03m
j03m

Reputation: 5303

(this was probably better as a comment, but got too long)

So, after reading this I was curious if pre-allocating was actually faster, because in theory it should be. However, this blog gave some tips advising against it http://www.html5rocks.com/en/tutorials/speed/v8/.

So still being unsure, I put it to the test. And as it turns out it seems to in fact be slower.

var time = Date.now();
var temp = [];
for(var i=0;i<100000;i++){
    temp[i]=i;
}
console.log(Date.now()-time);


var time = Date.now();
var temp2 = new Array(100000);
for(var i=0;i<100000;i++){
    temp2[i] = i;
}
console.log(Date.now()-time); 

This code yields the following after a few casual runs:

$ node main.js 
9
16
$ node main.js 
8
14
$ node main.js 
7
20
$ node main.js 
9
14
$ node main.js 
9
19

Upvotes: 8

Ivo Wetzel
Ivo Wetzel

Reputation: 46735

The array constructor has an ambiguous syntax, and JSLint just hurts your feelings after all.

Also, your example code is broken, the second var statement will raise a SyntaxError. You're setting the property length of the array test, so there's no need for another var.

As far as your options go, array.length is the only "clean" one. Question is, why do you need to set the size in the first place? Try to refactor your code to get rid of that dependency.

Upvotes: 3

Å ime Vidas
Å ime Vidas

Reputation: 185873

This will initialize the length property to 4:

var x = [,,,,];

Upvotes: 30

Related Questions