Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a range of natural numbers with spread syntax

On the one hand, a lack of an equivalent to Python 3's range is an annoyance in ES6. On the other hand, there are lots of workarounds. My question is why one workaround I tried actually works. To illustrate:

[...Array(10).keys()];

In case the reason I find this mysterious is not obvious, note that Array(10).keys() is at least apparently empty.

I'm aware this wastefully creates two arrays, as do most of the popular workarounds, and, that (at the cost of creating a generator function) using a generator can avoid that. E.g.,

[...(function*(){let i = 0; while(i<10) yield i++;})()];

My question is only about why the first workaround produces the desired result.

Edit:

Judging from the answers, some people believe that the evaluation of Array(10) is equivalent to the evaluation of Array.apply(null,Array(10)). They are not. For example, .hasOwnProperty(0) is false for the former but true for the latter. However, I am open to being persuaded they are the same in some way that matters here, since my understanding is clearly lacking at some key point. I suspect that the answer is that the result of iterating over the keys is determine by the length property, which both share, rather than the actual array indexes that have been defined. If so, I would like to know that this behavior is normative.

like image 616
Alan Avatar asked Mar 08 '23 06:03

Alan


2 Answers

Array#keys returns an Array iterator. Spread syntax then completely exhausts that iterator by accessing the next value in the iterator until there are no more values. Then it collects all the values from the iterator and spreads them into a new array.

Array(10) creates an array exotic object that does not have any actual integer-indexed keys, but just a length property -- so it would 'empty' but Array(10).keys() is not. The fact is that using Array#keys doesn't depend on actual elements, just the length property. The internal operation CreateArrayIterator creates a key iterator from an array by creating an iterator via the intrinsic %ArrayIteratorPrototype% object. Looking at %ArrayIteratorPrototype%.next(), you'll see that the length of the array is used. For Array#keys the index is continually incremented until it reaches the length of the array. That's how the iterator is created that gives you all the keys of the array without actually having said integer keys in the first place.


If you're curious about the abstract steps, see Section 12.2.5.2 ArrayAcculumation of the ECMAScript Language Specification, particularly the SpreadElement : ... AssignmentExpression production which outlines the process of stepping through an iterator that is used in conjunction with spread syntax.

To see the abstract steps for collecting these values into a new array, see Section 12.2.5.3 Evaluation. Specifically, the ArrayLiteral : [ ElementList ] production is the production [...Array.keys()] falls under. The aforementioned ArrayAcculumation process is performed which aggregates iterates through the iterator and sets them into the new array.

like image 165
Andrew Li Avatar answered Mar 16 '23 21:03

Andrew Li


Array.prototype.keys is returning a new Array Iterator.
Array(10) is an array of 10 (empty) slots.

When you spread the array's keys, you are iterating over it and creating a new array. But this time the new array's items are the slots from the first array.

You can see it with for of loop:

const arr = Array(10).keys();
for (let key of arr) {
  console.log(key);
}

By the way, you can use Array.from which takes a map function as it's second arguments. so you can just return the index or whatever you want:

const arr = Array.from(Array(10), (_, idx) => idx);
console.log(arr);

Edit
As a followup to your edited question about the Array(10) result:
The DOCS mentions:

If the only argument passed to the Array constructor is an integer between 0 and 232-1 (inclusive), this returns a new JavaScript array with its length property set to that number (Note: this implies an array of arrayLength empty slots, not slots with actual undefined values). If the argument is any other number, a RangeError exception is thrown.

So you actually get:

  • A new array
  • The length property is updated with the number provided to the function

  • You get n-slots

Now .keys will create an Iterator, which will return each slot index as the value.
for example:

let it = Array(10).keys();  

and calling:

it.next();

Will return:

{value: 0, done: false}  

Calling it again and again will yield

{value: n, done: false}

up until it will get to the last slot:

{value: 9, done: false}

then the next .next call will return

{value: undefined, done: true} 

which will flag that this is the end of the iterations.

running example:

const it = Array(10).keys();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next()); // done: true
like image 37
Sagiv b.g Avatar answered Mar 16 '23 21:03

Sagiv b.g