Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at some array operations with holes.
Array Operations and Holes
Various operations treat array holes differently.
Iteration is done with the Array.prototype[Symbol.iterator]
call.
It treats holes as if the entry is undefined
.
We can see this if we call Symbol.iterator
directly.
For instance, we can write:
const arr = [, 'foo'];
const iter = arr[Symbol.iterator]();
console.log(iter.next())
console.log(iter.next())
We return the iterator from to let us get the elements of the array sequentially with an iterator.
If we call next
the first time, we get:
{ value: undefined, done: false }
And if we call next
again, we get:
{ value: "foo", done: false }
As we can see, the value
is undefined
is returned from the first next
call.
The spread operator also treats holes the same way.
For instance, we can write:
const arr = [, 'foo'];
console.log([...arr]);
After we spread the arr
array, we can see the logged value is:
[undefined, "foo"]
The for-of loop also treats holes the same way.
For instance, if we write:
const arr = [, 'foo'];
for (const x of arr) {
console.log(x);
}
then we get undefined
and 'foo'
.
Array.from()
uses iteration to convert an iterable object to an array.
This works exactly like the spread operator.
For instance, if we have:
const arr = [, 'foo'];
console.log(Array.from(arr));
Then we get:
[undefined, "foo"]
If we pass in an array-like object that isn’t iterable into the Array.from
method, the missing entry is still treated as undefined
.
If we have:
const arr = Array.from({
1: 'foo',
length: 2
})
then we get the same result.
With a second argument, Array.from
works like Array.prototype.map
.
For example, if we write:
const arr = Array.from([, 'foo'], x => x)
console.log(arr);
Then arr
is [undefined, “foo”]
.
We can also use it to get the index. To do that, we write:
const arr = Array.from([, 'foo'], (x, i) => i)
then arr
is [0, 1]
.
Array.prototype.map
skip holes, but preserve them.
For instance, if we have:
const arr = [, 'foo'].map(x => x)
console.log(arr);
Then we get:
[empty, "foo"]
The hole stays as an empty slot after mapping.
It acts the same way if we map an array with holes to an array of indexes.
If we write:
const arr = [, 'foo'].map((x, i) => i)
we get [empty, 1]
.
The behavior of array holes is treated differently by different array instance methods.
forEacg
, filter
, and some
ignore holes.
map
skips but preserves holes.
join
and toString
treat holes like they’re undefined
,
null
and undefined
are treated as empty strings.
copyWithin
creates a hole when copying holes.
entries
, keys
, values
, find
, and findIndex
treat each hole as if they’re undefined
.
fill
doesn’t care whether there are holes or not.
concat
, map
, push
, reverse
, slice
, sort
, splice
and unshift
all preserve holes.
pop
and shift
treat them as elements.
Conclusion
Different operators and methods treat array holes differently.