Generating Arrays in JS
Tags: arrays, spread, iterators, javascript
Published: 2019-08-17 14:15:35

It doesn't come up very often, but there is always a time in a young programmers life where they have to generate an array of numbers. Well... not always. Some programmers sometimes might maybe need to... well **I** did it once! 

One such use case is in frontend frameworks where you need to display a set of numbers on a `select` menu. I wouldn't personally recommend using a `select`, but if it comes to a point where you're asked to by a client or boss, saying "no" doesn't fly so well. 

Let's start with the simplest option and keep working up to more and more complex examples.

# The For Loop

For all of our examples, let's try to generate the numbers 0-60 inclusive. Let's pretend we're using it for a user to choose a specific second or minute in a form. The for loop is probably the first example people think of when approached with this problem.

```js
const arr = [];
for (let i = 0; i < 61; i++) {
  arr.push(i);
}
```

We're simply incrementing `i` and adding `i` onto a predefined array each time we do increment. At the end of the day we get an array with 61 elements, 0-60 inclusive.

This approach is fine, but it's not "`functional`" as it deals with a statement. This means we can't inline this in `JSX` if we wanted to. We'd have to throw this into a function and call it in the `render`. This isn't "bad" necessarily, just a bit extra.

# The Array function

While we can pass comma-separated elements to `Array()`, in order to create a new array, we can also supply just a single parameter. This would be a number which describes the length of the array to generate.  This is a bit of a pitfall for us to keep in mind:

```js
Array(50, 5)        // -> [50, 5]
Array(50, 5).length // -> 2

Array(50)           // -> [empty × 50]
Array(50).length    // -> 50
```

What you might also notice is that we're creating an **empty** array with a length of `50`. We do *not* have 50 elements. This is the same as doing:

```js
const arr = []
arr.length = 50;
```

These are called array "holes". We're used to `undefined` taking place of undefined variables, but we're not actually changing anything except for the length of an empty array.

Now, we might think that we'd be able to generate an array with numbers 0-60 by just doing:
```js
Array(61).map((_, i) => i) // -> [empty × 61]
```
but you'd be wrong. We are unable to iterate over `empty` items. 
Dr. Axel Rauschmayer talks about it more in depth [here](https://2ality.com/2015/09/holes-arrays-es6.html) and [here](http://speakingjs.com/es5/ch18.html#array_holes), but we're essentially going to need to fill our array with *something* in order to iterate over it.
We can do that one of 2 ways - using `Array.prototype.fill` or `Function.prototype.apply`. 

```js
Array(61).fill()             // -> [undefined x 61]
Array.apply(null, Array(61)) // -> [undefined x 61]
```

I'd recommend the former (`.fill()`) since it's a bit more readable and understandable. This turns our final expression into:

```js
Array(61).fill().map((_, i) => i)
```

What if we wanted it to get a bit clearer?

# Using Array.from

`Array` has another method used a bit more with what is refereed to as "Array-like" data structures. `Array.from` can be used to convert any object with a `length` property into an array.

You might have seen `Array.from` used in contexts like dealing with DOM nodes: 

```js
const divNodeList = document.querySelectorAll('div');
const divArr = Array.from(divNodeList);
const texts = divArr.map(el => el.textContent);
```

`Array.from` will iterate over the numbered properties of the object until it hits the length property and replaces whatever it can't find with `undefined`. We can actually recreate it fairly easily with JS:

```js
const getArr = obj => {
  const arr = [];

  for (let i = 0; i < obj.length; i++) {
    arr.push(obj[i]);
  }
  
  return arr;
}
```

This, funny enough, is actually a more optimized version of `Array.from`. The bigger difference is that `Array.from` allows a few more parameters and accepts an [iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), not just an array-like object. We'll get into iterables in the next section.

So how do we go about using `Array.from` in our problem? If we pass `Array.from` an object with **only** a `length` property, we will get undefined in each position, unlike `Array()`! 

```js
Array.from({})                  // -> []
Array.from({ 2: 4, length: 4 }) // -> [undefined, undefined, 4, undefined]
Array.from({ length: 61 })      // -> [ undefined x 61 ]
Array.from({ length: 61 }).map((_, i) => i) // 0-60 inclusive
```

The cool thing here is that `Array.from` accepts a second parameter - a map function! This means we can move our map inside the parentheses:

```js
Array.from({ length: 61 }, (_, i) => i)
```

# Iterators and Iterables

This should probably be its own post, but essentially we have what is referred to as "iterators". We loop over certain data structures without needing to access anything to do with an index. The data structure itself handles what the next value will be.

The topic is a bit much for this post, so I suggest checking out the [MDN page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) for more information, but it's a really cool part of JS that allows the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) and [for...of loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) to work.

Iterator functions get kinda complex when dealing with internal state, so we have [Generator functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) to help us create them.

```js
function* makeIterator() {
  yield 2;
  yield 3;
  yield 'bananas';
}

[...makeIterator()] // -> [2, 3, 'bananas']
```

We can think of each `yield` as an element of the array in the order they appear. We use the spread syntax and surround it with brackets to turn it into an array. Also note how we require a `*` to differentiate this from a normal function.

We can also use loops inside generator functions to yield many times

```js
function* makeIterator() {
  for (let i = 0; i < 4; i++) {
    yield i;
  }
}

[...makeIterator()] // -> [0, 1, 2, 3]
```

Data structures are iterable if they contain an `@@iterator` property. This iterable is "well-formed" if the property follows the iterator protocol. We can give an object this property through `Symbol.iterator` and we can follow the protocol by using a generator function.

We can also follow the protocol in other ways, but they're more than we're going to go through in this post. 

Let's try to solve our problem using an iterable!

```js
const iterable = {
  [Symbol.iterator]: function*() {
    yield 2;
    yield 3;
    yield 'bananas'
  }
};

[...iterable] // -> [2, 3, 'bananas']
```

We have moved from a function to an iterable object. Now let's move the yields into a loop.

```js
const iterable = {
  [Symbol.iterator]: function*() {
    for (let i = 0; i < 61; i++) {
      yield i;
    }
  }
};

[...iterable] // 0-60 inclusive
```

Since we have an object, which is an expression, let's see if we can compress this down into 3 lines.

```js
[...{*[Symbol.iterator]() {
    for (let i = 0; i < 61; i++) yield i;
}}]
```

Nice! Not the prettiest, but it does what we want. Note that I've also changed `Symbol.iterator]: function*()` into `*[Symbol.iterator]()` as it's a bit shorter.

It should also be noted that all arrays are iterables. That's how they're able to be used with the spread syntax. The spread syntax also turns array holes into `undefined`. That means we can change our `Array()` example into:

```js
[...Array(61)].map((_, i) => i)
```

which honestly looks a bit cleaner. We can even use an array buffer, a concept we're also not going to talk too much about, with the spread syntax for the same result!

```js
[...new Uint8Array(61)].map((_, i) => i)
```

# Preferences

Now we're down to which one to use. 
We have a lot of options. When programmers have a lot of options we generally look at 2 things: style and performance. 

With JS, it is generally said to not look at performance benchmarks as JIT compilers might optimize solutions to be faster one day where it wasn't faster the day before. Performance benchmarks, due to engine optimizations, are also many times extremely misleading.

With that in mind, the mutable array option seems to be consistently the fastest. Using `Array()` with `.fill()` or the spread syntax seems to come second, iterators third, and `Array.from()` the last.

`Array.from` can be recreated with a basic function for most use cases and be a better form of `Array.from` if it's specialized for its specific use case, but unless you're calling it many times a second, I wouldn't sweat it.

The `Array()` option with spread syntax seems to be the cleanest, but creating your own class for this very problem always seems a lot more fun:

```js
class Range {
  constructor(min, max, step = 1) {
    this.val = min;
    this.end = max;
    this.step = step;
  }
  
  * [Symbol.iterator]() {
    while (this.val <= this.end) {
      yield this.val;
      this.val += this.step;
    }
  }
}
```

Now you can use `new Range(min, max[, step])` to generate an iterable of any range and just use the spread syntax to create arrays! A bit more verbose, but a bit more fun to use too!

What do you think? Any style preference?