Sparse arrays in JavaScript

Intro
Welcome to sparse arrays, a JavaScript concept that completely escaped me until now. Let's dig into it.
I wanted to create a game board, an array of 8 rows, themselves arrays of 8 elements so I unthinkingly did a const row=Array(8)
. I used a console.table
on it and it was displayed in a stupid way, but I blamed it on Google devs and moved on. I didn't understand something was amiss until I did a .forEach
on a row array that had only three pieces on it and it was executed just three times. What was going on?
Explanation
Well, when you use the Array(integer)
constructor you get a sparse array. It has a length, it has items, but it is not a contiguous memory space with N slots for values. The same thing happens when you omit an element from an array declaration, like [1,,2]
which is a sparse array different from [1, undefined, 2]
. A normal array, in this context, is called a dense array.
In other words, sparse arrays work differently in for and forEach loops! It's almost like an empty object with a length property that has array methods working for it. Think about this code for example:
const arr = { length: 8 }
arr[1]='something';
// displays 8 lines, where only the second is 'something' and the rest are undefined
for (let i=0; i<arr.length; i++) console.log(arr[i]);
// simulated forEach - displays only 2 lines: something and 8
Object.values(arr).forEach(v=>console.log(v));
The simplest way to transform a sparse array into a dense array is to iterate it, like this: [...sparseArray]
. You can create a dense array with Array.from( { length:8 } )
, however new Array(8)
or Array(8)
will return a sparse array.
If you look in the docs, sparse arrays are often used with .fill
, like this: Array(8).fill(undefined)
, which will return an array of 8 undefined elements.
Going deeper
So, wait... now there are two types of arrays, they function differently, they must be different classes, right? Actually, no! These are just concepts that apply to Array. Let's see some code:
const x = [1]; // dense array
x.length=10; // sparse array, all slots except the first are 'empty'.
x.fill(2); // dense array, all slots are filled with the value 2
x[1]=undefined; // dense array, second slot contains undefined
delete x[3]; // sparse array, fourth slot is 'empty'
// 1 in x == true;
// 3 in x == false;
What is this madness? This is something new introduced by nasty people who just want to make everything complicated, right? Actually, no. Arrays were always sparse, from the very beginning of the language in 1995. The concept itself was formalized in 2009 for ES5, though, where it was explicitly said that for..in and for..of and forEach, map, filter, etc, will skip empty slots.
Conclusion
JavaScript arrays are misnomers and different from the previous concept of array, used in languages like C or .NET, a fixed and contiguous memory space to store elements based on a positional index. JavaScript arrays are complex objects that allow insertion and removal of items, resizing and, yes, sparsity.
You can work in JavaScript for years and years and still get blindsighted by something like this...