Original link: 2ality.com/2018/12/cre…

The best way to create arrays is to use literals:

Const arr = 0, 0;Copy the code

But it’s not always the best solution, for example when creating large arrays. This blog explores what to do in these situations.

1. Arrays with no empty elements tend to perform better

In most programming languages, arrays are contiguous sequences of values. In JavaScript, Array is a dictionary that maps indexes to elements. It can have empty elements, and empty elements have their own indexes, but they don’t map to elements. For example, the following array has an empty element at index 1.

> Object.keys(['a'.'c'[])'0'.'2' ]
Copy the code

Arrays with no empty elements are called dense arrays, and dense arrays tend to perform better because they can be stored consecutively (internally). Once there is at least one empty element, the internal representation must change. There are two options:

  • Dictionaries, lookups take more time and are more expensive to store.
  • A contiguous data structure with token values for empty elements. Extra time is required to check if the value is an empty element. In either case, if the engine encounters an empty element, it can’t just return itundefinedInstead, you have to traverse the prototype chain and search for an attribute named the empty element index, which takes more time.

In some engines, such as V8, switching to a lower performance data structure is permanent. Even if all empty elements have values added, they do not switch back.

For more information on how V8 represents arrays, see Element types in V8 by Mathias Bynens.

2. Create an array

Array constructor

It is common to use the array constructor to create an array of a given length.

const LEN = 3;
const arr = new Array(LEN);
assert.equal(arr.length, LEN);
// arr only has holes in it
assert.deepEqual(Object.keys(arr), []);
Copy the code

This approach is convenient, but it has two drawbacks:

  • Even if you fill in the values completely later, these empty elements will make the array slightly slower.
  • An empty element is rarely the initial “value” of the element. For example,0More common.

2.2 The array constructor adds the.fill() method

The.fill() method changes an existing array and populates it with the specified value. This helps initialize an Array after it is created with a new Array() :

const LEN = 3;
const arr = new Array(LEN).fill(0);
assert.deepEqual(arr,[0,0,0]);
Copy the code

Warning: If you.fill() an array with objects, all elements reference the same instance (that is, the object is not cloned) :

const LEN = 3;
const obj = {};

const arr = new Array(LEN).fill(obj);
assert.deepEqual(arr, [{}, {}, {}]);

obj.prop = true;
assert.deepEqual(arr,
  [ {prop:true}, {prop:true}, {prop:true}]);Copy the code

We will later encounter a method of populating without this problem (via array.from ()).

2.3. Push () method

const LEN = 3; shu const arr = [];for (let i=0; i < LEN; i++) {
  arr.push(0);
}
assert.deepEqual(arr, [0, 0, 0]);
Copy the code

This time, we create and populate an array without adding empty elements. Therefore, using an array after creating it should be faster than using an array constructor. Alas, the creation of the array is slower because the engine has to reassign the contiguous internal representation multiple times as it grows.

2.4 Populating an undefined array

Array.from() converts values of type iterables and like-array to arrays. It treats an empty element as an undefined element. We can use it to convert each empty element to undefined:

> Array.from({length: 3})
[ undefined, undefined, undefined ]
Copy the code

The {length: 3} argument is an array-like object of length 3, containing only empty elements. New Array(3) can also be used, but larger objects are usually created.

Array extensions apply only to iterable values and have similar effects to array.from () :

> [...new Array(3)]
[ undefined, undefined, undefined ] 
Copy the code

Alas, array.from () creates its result with new Array(), so you still get a sparse Array.

2.5 Mapping using array.from ()

If you provide a mapping function as its second argument, you can use array.from () to map.

Populate the array with values

  • Create an array of small integers:
> array. from({length: 3}, ()=> 0) [0,0,0]Copy the code
  • Create arrays with unique (non-shared) objects:
> array. from({length: 3}, ()=>({})) [{}, {}, {}]Copy the code

Create integer value ranges

  • Create an array with an ascending integer:
> array. from({length: 3}, (x, I)=> I) [0,1,2]Copy the code
  • Create an arbitrary integer range:
> const START = 2, END = 5; > array. from({length: end-start}, (x, I)=> I + START) [2,3,4]Copy the code
  • Another way to create arrays with ascending integers is through.keys(), it also treats vulnerabilities as undefined elements:
> [... new Array(3).keys()]
[0,1,2]
Copy the code

.keys() returns an iterable. We use the extension operator to convert it to an array.

3. Add: Create an array

  • Fill an empty element or undefined:
New Array(3) → [,,,] Array. From ({length: 2}) → [...new Array(2)] → [undefined, undefined]Copy the code
  • Fill any value:
const a=[]; for (leti=0; i<3; i++) a.push(0); - > [0, 0, 0] new Array (3). The fill (0) - > [0, 0, 0] Array. The from ({3} length:, () = > ({})) - > [{}, {}, {}] (unique objects)Copy the code
  • Integer range:
Array.from({length: 3}, (x, I) => I) → [0, 1, 2] const START=2, END=5; Array. The from ({length: END - START} (x, I) = > I + START) - > [2, 3, 4] [... new Array (3). The keys ()] - > [0, 1, 2]Copy the code

3.1 Method Recommendation

I prefer the following approach, which focuses on readability rather than performance.

  • You need to create an empty array that you’re going to fill completely, and then what?
new Array(LEN)
Copy the code
  • Do you need to create an array initialized with the original values?
new Array(LEN).fill(0)
Copy the code
  • Do you need to create an array initialized with objects?
Array.from({length: LEN}, () => ({}))
Copy the code
  • Do you need to create a series of integers?
Array.from({length: END-START}, (x, i) => i+START)
Copy the code

If you’re dealing with arrays of integers or floating-point numbers, consider arrays of types created for that purpose. They cannot have empty elements and are always initialized with zero.

Tip: Array performance is usually not that important

  • In most cases, I wouldn’t worry too much about performance. Even an array of empty elements is fast. We can think a little bit more about making code understandable and meaningful.

  • In addition, the way and location of engine optimization is changing all the time. Tomorrow will always be faster than today.

4. Thank you

Thanks to Mathias Bynens and Benedikt Meurer for helping me learn the V8 details.

5. Read further

  • The chapter “Type Arrays” in “Exploring ES6”
  • “ES6 and empty Elements in arrays” in “Explore ES6”