Skip to content Skip to sidebar Skip to footer

Why Does Spread Syntax Convert My String Into An Array?

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?

Solution 1:

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 ]

  1. Let iterator be GetIterator(value).
  2. ReturnIfAbrupt(iterator).
  3. Let iteratorRecord be Record {[[iterator]]: iterator, [[done]]: false}.
  4. 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).
  5. Let result be the result of performing IteratorDestructuringAssignmentEvaluation of AssignmentRestElement with iteratorRecord as the argument.
  6. If iteratorRecord.[[done]] is false, return IteratorClose(iterator, result).
  7. 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.

Solution 2:

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.

Solution 3:

Spread syntax can be applied only to iterable objects. Since String is iterable Spread operator works fine and splits your char array(String) in to char's.

You can check that with below sample which demonstrate that String is iterable by default.

var s = 'test';    
for (k in s) {
  console.log(k);
}

And ECMAScript6 specification even mentioned about this specific String case.

Spread Operator

Spreading of elements of an iterable collection (like an array or even a string) into both literal elements and individual function parameters.

http://es6-features.org/#SpreadOperator

var str = "foo";
var chars = [ ...str ]; // [ "f", "o", "o" ]

And it is worth mentioning that this is a specific case and only happens when you use a direct String with spread operator. When you give a single String inside an array, the whole array will be treated as the iterable object and not the string inside.

var str = [ "hello" ,2 ];
var other = [  ...str ]; // [  "hello" ,2 ]

I know the above example doesn't make much sense, but just to convey the fact that the String will be treated differently in this case.

Solution 4:

There are two things going on here.

First, you're using array destructuring. This allows you to take an iterable and assign values from it to individual variables.

Second, you're using a rest parameter. This converts any remaining output of the iterable into an array.

So your string 'hello' is iterated into its individual characters, the first one is ignored (because you omitted the destination), then the remaining characters are turned into an array and assigned to w.

Solution 5:

Because a string in javascript is sort of treated like an array of characters.

For example, when you do a for each loop over a string (let's say hello), with lodash:

_.forEach('hello', function(i) {
    console.log(i)
})

it outputs:

h
e
l
l
o

Functions like slice() also work on both strings and arrays.

Post a Comment for "Why Does Spread Syntax Convert My String Into An Array?"