Search code examples
javascriptarraysobjectkey

Javascript unusual behavior regarding Array containing 1...N using spread operator


In Javascript, I make this simple ascending array by spread operator.

[...Array(5).keys()]
// printed list
[0, 1, 2, 3, 4]

But I simply added 1 and this unexpected array generated. (I just expected it would be an error)

[...Array(5).keys() + 1] 
// printed list
["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]

I would like to know what makes this behavior.


Solution

  • First, the expression to the right of the ... is evaluated. That is, Array(5).keys() + 1 gets evaluated, and the result of that evaluation is then spread. Grouping the expression may make things a little clearer:

    [...(Array(5).keys() + 1)] 
    

    So, above, we start with calculating the result of Array(5).keys() + 1. The + operator works between primitive operands. When you do Array(5).keys() you get an iterator back which is not classified as a primitive. So, JS will try and convert it to a primitive. When JS tries to convert the iterator into a primitive in this context it tries to:

    1. Invoke the iterator's Symbol.toPrimitive method, since this doesn't exist, it moves onto trying the next step
    2. Invoke the .valueOf() method of the iterator. This succeeds, but it returns the iterator back, which is not a primitive, so we go to the next step
    3. Invoke the .toString() method. This results in a non-object type (ie: a string), and so this is successful.

    When the iterator's .toString() method is called it gets evaluated to "[object Array Iterator]". As this then gets added with the number 1, we concatenate the string with the number (rather than add), giving:

    [..."[object Array Iterator]1"]
    

    Since strings are iterable, the spread syntax will use the string's iterator to loop through the code points in the string, resulting in every character of the string becoming its own element:

    ["[", "o", "b", "j", "e", "c", "t", " ", "A", "r", "r", "a", "y", " ", "I", "t", "e", "r", "a", "t", "o", "r", "]", "1"]
    

    In order to add one to each item in your iterator, you can use Array.from() which can take an iterator, and applies a mapping function to each element:

    const res = Array.from(Array(5).keys(), i => i+1);
    console.log(res);

    Since the argument to Array.from() doesn't have to be an iterator, you could also just use Array(5) instead and use the index as the value to add to:

    const res = Array.from(Array(5), (_,i) => i+1);
    console.log(res);