Why does spread syntax convert my string into an array?
var v = 'hello'; var [, ...w] = v; // ["e", "l", "l", "o"]
Why is w
not a string?
We can use the spread operator on iterables like a String or an array and it'll put the contents of the iterable into individual elements.
The spread operator unpacks elements of iterable objects such as arrays, sets, and maps into a list. The rest paramter is also denoted by three dots (…). However, it packs the remaining arguments of a function into an array. The spread operator can be used to clone an iterable object or merge iterable objects into one.
This means that if you have a reference data type stored inside your array/object, when you make a copy with the spread operator, the nested array/object will contain a reference to the original and will thus be mutable.
Spread syntax (actually a punctuator as noted by RobG) allows for iterables to be spread into smaller bits. Since strings are iterables (they're character arrays internally, more specifically ordered sequences of integers representing characters), they can be spread into individual characters.
Next, destructuring assignment is performed on the array to unpack and group the spread values. Since you ommit the first element of the character array with ,
and don't assign a reference, it's lost, and the rest of the iterable object is saved into w
, spread into it's individual parts, single characters of the character array.
The specific semantics of this operation are defined in the ECMAScript 2015 Specification by the ArrayAssignmentPattern : [ Elisionopt AssignmentRestElement ] production:
12.14.5.2 Runtime Semantics: DestructuringAssignmentEvaluation
with parameter value
[...]
ArrayAssignmentPattern : [ Elisionopt AssignmentRestElement ]
- Let iterator be GetIterator(value).
- ReturnIfAbrupt(iterator).
- Let iteratorRecord be Record {[[iterator]]: iterator, [[done]]: false}.
- If Elision is present, then
a. Let status be the result of performing IteratorDestructuringAssignmentEvaluation of Elision with iteratorRecord as the argument.
b. If status is an abrupt completion, then
i. If iteratorRecord.[[done]] is false, return IteratorClose(iterator, status).
ii. Return Completion(status).- Let result be the result of performing IteratorDestructuringAssignmentEvaluation of AssignmentRestElement with iteratorRecord as the argument.
- If iteratorRecord.[[done]] is false, return IteratorClose(iterator, result).
- Return result.
Here, Elision refers to an omitted element when spreading with one or more commas (,
), comparable to omitted syllables as the name suggests, and AssignmentRestElement refers to the target that will receive the spread and destructured values, w
in this case.
What this does is first get the iterator of the object, from the internal @@iterator
method and steps through that iterator, skipping however many elements indicated by the elision's width by the Elision production in IteratorDestructuringAssignmentEvaluation. Once that's done, it will step through the iterator of the AssignmentRestElement production, and assign a new array with all the spread values -- that's what w
is. It receives the spread out single-character array, unpacked to exclude the first character.
The @@iterator
method in which the iteration is gotten from is a well-known Symbol and changing it for an object can change how it's iterated, as in Emissary's answer. Specifically, the default implementation of the @@iterator
method for String is as follows:
21.1.3.27 String.prototype [ @@iterator ]( )
When the @@iterator method is called it returns an Iterator object (25.1.1.2) that iterates over the code points of a String value, returning each code point as a String value.
Thus, the iterator allows for iteration through single code points, or characters of a string -- and consequently spreading the string will result in an array of its characters.
In ES2015 the spread syntax is specifically acting against the internal String @@iterator
property - any object can be iterated in this way by assigning your own iterator or generator / function*
to the obj[Symbol.iterator]
property.
For example you could change the default behaviour of your new array...
const a = [...'hello']; a[Symbol.iterator] = function* (){ for(let i=0; i<this.length; ++i) yield `${this[i]}!`; }; console.log([...a]);
You could change your string iterator too but you'd have to explicitly create a String
object.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With