Kyle Simpson proved I STILL don't know JavaScript (arrays)
Tags: javascript, todayilearned
Published: 2019-12-06 17:26:49

If you've never read Kyle Simpson's fantastic series, [You Don't know JS](https://www.amazon.com/gp/product/B07FK9VBD7/?ie=UTF8&%2AVersion%2A=1&%2Aentries%2A=0), I'd first encourage to stop reading this, go read _that_ , and then come back.  It might take about six weeks, but that's fine. I'll wait. 

...
...
...

I see you came back. Great. Feel like you know JavaScript now? You sure? Go back and read it again. I'll wait. This post isn't going anywhere. 


...
...
...

Feel like you really know JS now? Fantastic.  Let's talk about that simplest of data structures, the array, and what I didn't know after reading that book series _four times_. 

## JavaScript, I thought I knew you...

First I want to point you to [this twitter thread](https://twitter.com/getify/status/1201203738129641473) where, in the [third tweet in the series](https://twitter.com/getify/status/1201272013429575681), Kyle makes this statement:

> just for clarity sake, this is what I find surprising...

![kyle simpson showing an array created with commas in foreach and for of where the foreach doesn't show undefined but the for of does](https://pbs.twimg.com/media/EKvGh0vXsAEHdIq?format=png&name=small)



Now, I looked at that image and thought, "this makes sense. But... why does it make sense?"

The more I tried to think about why it made sense, the more I realized I didn't know JavaScript. 

## First, a primer on making arrays

There's a few different ways to make arrays in JavaScript. They amount to, "you can instantiate them with data, or without data". 

### Instantiating with data

Sometimes we need to write an array with data already in it. It's not uncommon to do this when you don't expect that list to really change after you declare it. So there's two ways we instantiate that array: 

#### Using the `Array` constructor

```
const mistakes = new Array('my liberal arts degree', 'eating 15 slim jims')`
```
This is considered weird and generally a bad practice in JavaScript. I'll explain why in a bit. 

#### Using the Array Literal
```
const moreMistakes = [tequila', 'Michael Bay\'s TMNT movie'];
```

This is the more common approach that I hope most of us are using at home.  

### Instantiating without data
In cases where we're moving data from one data structure to another we often declare an empty array and _then_ modify it.

An extremely common pattern is declaring the empty array and then pushing to it:


```
const moreFutureMistakes = [];

moreFutureMistakes.push('TMNT sequels')
```

But if you want to be _that person_, of course you can use the array constructor:
```
const moreUnusualMistakes = new Array();

moreUnusualMistakes.push('what you\'re going to see next');
```

### Weird ways to instantiate arrays
I don't think I've ever really seen these in the wild, but they've always been there in the back of my mind. Kind of like the Hawaii 5-0 theme song. It's not really doing anything in my brain other than sitting there. Making sure I don't forget it. 

One of the only things I remember from my C++ class was that arrays had to have sizes. And I didn't know why. I still don't know why. (answer:`somethingSomething[memory]`)

So three weird ways of instantiating arrays involve setting the size of it up front: 

```
const superSizeMe = [];
superSizeMe.length = 3; // SURPRISE! Length is a setter

const preSized = new Array(3); // "This won't confuse anyone," said no one ever.

const commaSized= [,,,]; 

const isWeirdButTrue= (superSizeMe.length === preSized.length === commaSized.length);

```

If you were wondering why it's considered bad practice to use the `Array` constructor, now you know. It's because if you give it exactly one argument, and that argument is an Integer, it will create an array of that size. So the array constructor could get unexpected results when dealing with numbers. 

And it's not like any of the other approaches are remotely close to best-practice, either. They're all the strange practices of someone who's maybe a little too curious for their own good or perhaps wondering if the trickster-god Loki is in fact alive and designing programming languages. 

## Weird Instantiations and Weird ways to set data result in ~~weird results~~ totally expected behavior. 


Now we're caught up and back to Kyle's tweet about how weird this is:

```
[,,,3,4,,5].forEach(x=>console.log(x));
// 3
// 4
// 5
```
1. Ok, let's agree that comma-instantiated arrays is weird.
2. Ok. It logged... 3,4,5 

This is fine. Everything's fine. those other slots must be `undefined` or unavailable. 


```
for (let x of [,,,3,4,,5]) { console.log(x); }
// undefined 
// undefined
// undefined
// 3
// 4
// undefined
// 5
```

Hold up....

### What's in those "weirdly instantiated arrays" ?

Let's take a step back and look at these pre-sized arrays:
``` 
const myRegrets = new Array(3);
const moreRegrets = [,,,];
const noRegerts = [];

noRegerts.length = 3;
```


If you're using Firefox, crack open the console, run this, and take a look at those arrays. 

You might see something like:
```
Array(3) [undefined, undefined, undefined]
```

### But is that array *really* filled with three `undefined`?

No. Not it is not. That's what Kyle Simpson is aptly pointing out. If you loop through these "pre-sized" arrays, and try to log the value, you will not get a log: 

```
const myRegrets = new Array(3);
myRegrets.forEach((regret, regretIndex) => {
  console.log(regret, regretIndex);
});


for (regretName in myRegrets) {
 console.log(regretName, myRegrets[regretName]);
}

```
So, the first big takeaway here is that a pre-sized array, *or* an array created with comma/slots, doesn't have a _value_ in those slots. 

`myRegrets` is not an array with 3 `undefined`. It's an array with three slots of _nothing_. 

To further prove this point, add an _actual_ `undefined` to the third slot:

```
const myRegrets = new Array(3);
myRegrets[1] = undefined; 
myRegrets.forEach((regret, regretIndex) => {
  console.log(regret, regretIndex);
});


for (regretName in myRegrets) {
 console.log(regretName, myRegrets[regretName]);
}

```

You got a log, didn't you? Just one, right? 

*Double-you Tee Eff*

## There's an _implicit_ and _explicit_ `undefined` in Arrays

This is what I think Kyle is talking about here. 

When we do these weird Array tricks where we pre-size it, or we comma-slot it (e.g. `[,,undefined]`), JavaScript isn't actually putting values in those slots. Instead, it's saying the slots _exist_ ... _kinda_. 

If something exists, but has no value, we have a name for that:

`undefined`


```
const myRegrets = [,,undefined];
const youWillRegretThis;

myRegrets[0] === youWillRegretThis; // true, so very true
```

But I call this "implicit undefined" because this doesn't get logged in any looping that we do. Not `forEach`, neither `for - in`, nor `map` and its buddies will log a slot that has no value; i.e. implicitly undefined;

You could also call this "undeclared" if you don't like "implicitly undefined". 

### Explicit `undefined` must take up memory

When you loop over an array with an _explicit undefined_ , it must be taking up actual memory. And that's why it gets logged:

```
const myRegrets = [,,undefined];
 
myRegrets.forEach((regret, regretIndex) => {
  console.log(regret, regretIndex);
});
// will log 3

for (regretName in myRegrets) {
 console.log(regretName, myRegrets[regretName]);
}

// will log 3
```


So just remember this, kids. There's an implicit undefined and an explicit undefined when it comes to arrays. 

This probably won't be a thing you trip on any time soon unless you merge arrays. 

Or use `for of`...

Wait. `for of`?

yes.

*(Double-you tee ay eff)*

## `for - of` is the only looping mechanism that doesn't care about implicit or explicit undefined.

Again, think of "implicit undefined" as meaning, "undeclared":

```

const myRegrets = [,,undefined];
 

let regretCounter = 0;

for (regret of myRegrets) {
 console.log(regret, regretCounter++)
}
// undefined 0
// undefined 1
// undefined 2
```

Why did it log all three?

I don't know for certain, but I have a _theory_ that a much smarter person than I needs to investigate. 

The `for of` pattern implements an [iteration protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)

My theory here is that the iteration protocol on arrays behaves something like what's shown from the [examples page](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#Iterator_examples):

```
function makeIterator(array) {
    var nextIndex = 0;
    
    return {
       next: function() {
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    };
}
```

if `for - of` implements something like this under the hood, this will iterate by index, not property (and arrays are really just objects with properties that are numbers. kinda).

So, back to what we call a thing that exists but has no value. You know the one. Our old friend. The one we never invite to parties but he shows up anyway? Remember him? 

`undefined`

I'm really starting to not like that guy. He weirds me out.



TL;DR

1. The `.length` property of an array isn't the same as saying, "these many slots have value"
2. Arrays can have "implicit undefined", and "explicit undefined", depending on whether the space really has a value and "implicit undefined" is more like, "undeclared"
3. If you don't create arrays in weird ways, or do weird things with arrays, you'll likely never encounter this
4. If you are that trickster god Loki, or a fan, and choose to create arrays in weird ways, or manipulate them weirdly, you may need to use `for of` to get the most consistent result while looping. 

JavaScript makes a lot of sense early in the morning. Not late at night.


#[Edit: Some additions]
_A few comments on this post made me do some research and testing that some may find useful_. This was already a long read, so unless you're camped out in a bathroom stall on account of a late-night binge on cheese, or you're otherwise really interested in talking about specifications, you can skip this stuff. 


## Are there array properties that are simply being set as not enumerable?
I don't _think_ so. 

I skimmed through some [ECMA specs](https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) to see if this behavior is defined anywhere. 

### The specs say that array elements without assignment expressions are not defined

Section 12.2.5 says

> Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. *Elided array elements are not defined.*

So if you have `[,,'foo']`, those array elements after the comma that don't have some sort of expression are "elided". 

Also worth noting is that the specs say that `['foo',]` does _not_ contribute to the length of the array.

Also _also_ worth noting is that I haven't yet found if pushing values to a random index above the length counts as elision. e.g.:
```
const gapped = [];
gapped[2] = "please mind the gap";
```

### The specs don't seem to state that array elements are created but not enumerable

Step 8 of section 22.1.1.3 describes how an array is created:

> Repeat, while k < numberOfArgs
> a. Let Pk be ! ToString(k)
> b. Let itemK be items[k]
> c. Let defineStatus be CreateDataProperty(array, Pk, itemK)
> d. Assert: defineStatus is true
> e. Increase k by 1

`Pk` is the key (which is an index) and `itemK` is the value. 

If the JavaScript engine is following this algorithm, an item, regardless of its value, gets passed into the `CreateDataProperty` function/method/whatever.

The question is, "does the first slot in `[,,'foo']` constitute being an _item_? 12.2.5 says no. (I think)

### But is there a chance `CreateDataProperty` is creating a property making it non-enumerable?

If you read in section 7.3.4, it doesn't give any logic or condition where the `enumerable` property in the descriptor . Steps 3 and 4 set the property to be enumerable:

> 3.  Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]:true }.
> 4.  Return ? O.[[DefineOwnProperty]](P, newDesc)

I haven't read through _all_ the specification has to say about arrays. But this seems further suggest that these "empty slots" are *really empty*. 

### What about the `in` operator. Will it find these empty slots?
No it will not!

```
const slotted = [,,'one'];
let i = 0;

while (i < slotted.length) {
 if (i++ in slotted) {
  console.log(`${i - 1} is in the array`);
 }
}
```
This will log exactly once, displaying `2 is in the array` in your console. 

If you have an _explicit_ `undefined`, however`const explicit = [undefined, undefined, 'one']` , it will log three times. 


## STL;SDR (Still too long, still didn't read)
First, let me caveat everything by telling you I'm way more qualified to talk to you about French Existentialism than JavaScript. The likelihood that all this is wrong is pretty high. 

Based on my understanding of the specifications, "implicit undefined" is a somewhat valid-ish way to describe a "slot" in an array that has no value. 

Except, of course, there's not really even a slot. There's not even the idea of a slot. A slot does not exist without a value. (#Existentialism)

[As Kyle Simpson points out](https://github.com/getify/You-Dont-Know-JS/blob/1st-ed/types%20%26%20grammar/ch1.md#undefined-vs-undeclared) there is a difference between `undefined` and `undeclared` but JavaScript doesn't always give you messaging that makes it clear which is which. 

This "implicit undefined" is more of an existential problem where we only have so many ways to describe Being and Nothingness. 

```
const existentialCrisis= [,undefined,'Waiting for Godot']`;
console.log(typeof existentialCrisis[1]); // undefined
console.log(typeof existentialCrisis[0]); // undefined

```