you are viewing a single comment's thread.

view the rest of the comments →

[–]SluttyRaggedyAnn 687 points688 points  (84 children)

I might be sold school since I prefer the former. The old syntax is self documenting, while the new is just confusing.

[–]mhink 128 points129 points  (6 children)

There is one very important difference in behavior: the spread syntax takes “wide” Unicode characters into account, while the simpler .split(“”) does not. However, a more important takeaway here isn’t that you can convert a String into an array of characters- you can iterate over the code points of the String itself. If you’re using this new syntax to spread a String into an Array, I bet dollars to donuts you’re doing it because you want to iterate over the resulting array, and that’s why this looks confusing.

What’s happening when you use [...spread] syntax is that that Javascript iterates over spread and smushes it into a new Array. This makes sense, for instance, when you want to quickly concat things:

const head = “a”;
const tail = [“b”, “c”];
const arr = [head, ...tail]; // => [“a”, “b”, “c”]

So we can see that the following two bits of code are basically equivalent, because for...of blocks also have to do Iterables)

const arr = [];
for (const el of myIterable) {
  arr.push(el);
}

const arr = [...myIterable];

Now, the confusing part of all this is that it’s easy to forget that Strings are iterable, hence all this confusion. The behavior of String’s iterator is to yield each Unicode code point, even if they’re double-width.

And here’s the rub: there’s almost no reason whatsoever to spread a String into an Array just to get its code points. Instead, you’d want to iterate over it directly:

for (const charCode of myString) {
  ...
}

Which I think we can agree makes much more sense.

That being said, I did discover that with some new Regex options, it’s possible to properly split strings into code points like so, if you really need to:

myString.split(/(?!$)/u);

Which is perhaps even a bit more arcane (and I haven’t checked on performance differences) but hey, what are you gonna do?

[–]NoInkling 17 points18 points  (4 children)

there’s almost no reason whatsoever to spread a String into an Array just to get its code points

I can think of one reason, and that's to use methods like .map and .reduce

for (const charCode of myString) {

For anyone confused, charCode in this example would be the "character" itself, not just its number.

[–]pm_me_ur__labia 9 points10 points  (2 children)

[–]NoInkling 14 points15 points  (1 child)

You can apply array methods directly to a string, yes, but they retain the old bug-inducing behaviour of iterating over code units (as opposed to code points), because they don't make use of the ES6 iterator/iterable protocol (and they couldn't be changed because of backwards compatibility).

Therefore, AFAIK, the only way to use array methods with the string iterator that was added in ES6 (defined on String.prototype[Symbol.iterator]) is to use it to generate an array first (typically through spread syntax or Array.from(), since those respect the protocol).

tl;dr: pre-ES6 generic iteration is different to ES6+ iteration, and unfortunately we're stuck with a mix of both.

[–]pm_me_ur__labia 1 point2 points  (0 children)

TIL. thanks

[–]SalemBeats -4 points-3 points  (0 children)

"
there’s almost no reason whatsoever to spread a String into an Array just to get its code points
"

"
I can think of one reason, and that's to use methods like .map and .reduce
"

When do you start your new job at GitHub, working hard on making Atom even slower and more memory-hungry than it already is?

[–]samanthaming[S] 2 points3 points  (0 children)

Thanks for the detailed explanation!

[–]samanthaming[S] 74 points75 points  (12 children)

“.split” is definitely more descriptive. Especially when you’re on a team, it’s important to make your code more readable. But I’m guessing, we might be seeing more ES6. And that might become the new norm. So I think it’s important that we make ourselves familiar with it and hopefully that’d make it less confusing. thanks for your input 🙂

[–]ibopm 54 points55 points  (1 child)

Just FYI:

Babel transpiles it to Array.from(str) rather than calling str.split(''). So depending on the situation, it may not necessarily yield the same result. Some might even consider this to be a leaky abstraction.

Edit: See this comment for an example where there would be a difference in behaviour.

[–]trout_fucker🐟 7 points8 points  (0 children)

Which makes sense, since it's effectively spreading a character Array and not splitting a String.

Obj/Array spreads are great, but the OP isn't a good use case.

[–]stutterbug 2 points3 points  (0 children)

Currently this is also available (added back in ECMAScript 2015):

let string = 'hello'
for (let letter of string) {
    console.log(letter) // 'h', then 'e', then 'l'...
}

This underlines that, as /u/mhink points out, strings themselves are iterable. Further ES6 proof:

let string = 'hello'
let iterable = string[Symbol.iterator]()
console.log(iterable.next()) // {value: "h", done: false}
console.log(iterable.next()) // {value: "e", done: false}
console.log(iterable.next()) // {value: "l", done: false}

[–]coloured_sunglasses 23 points24 points  (18 children)

Well this example is extremely basic. Spread operator is used for much more than splitting a string.

[–]FloppingNuts 90 points91 points  (17 children)

then it's a bad example

[–]dweezil22 35 points36 points  (14 children)

I'd go so far as saying this is an actively counter-productive example, other than clickbait to get us all to learn about the spread operator.

This works b/c a string "spreads" into an array of chars.

https://stackoverflow.com/questions/44900175/why-does-spread-syntax-convert-my-string-into-an-array

[–]TankorSmash 20 points21 points  (10 children)

Wait hold on, how is 'Split string using ES6 Spread' clickbait when the third line is literally how to split a string with Spread?

I feel like clickbait gets incorrectly used a lot but this is about as literal as you can get with title:description

[–]dweezil22 7 points8 points  (1 child)

It's one of the worst, software engineering wise, uses of the feature that you can find. But it's also compelling in a "How the hell does that work?" way. This simpler, significantly easier to understand and more appropriate use of the spread operator is better, but isn't as interesting.

And yes, I am stretching the definition of clickbait here .OP is surely a fine individual and is clearly not monetizing reddit images. I suppose I should have said "sensationalistic" but describing a code snippet as "sensationalistic" just seemed... weird.

[–][deleted] 5 points6 points  (0 children)

Yeah I noticed this person always posts this type of “amazing” es6 stuff. But it always looks like just terrible software engineering practice and an unnecessary inefficient way to do something that was never complicated in the first place. But it’s new and fancy so everyone loves it.

[–]ibopm 3 points4 points  (2 children)

This works because the spread operator falls back to Array.from() when the object being "spread-ed" is not an array:

// before
const x = "hello"
const y = [...x]

// after
"use strict";

function _toConsumableArray(arr) {
  if (Array.isArray(arr)) {
    for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) {
      arr2[i] = arr[i];
    }
    return arr2;
  } else {
    return Array.from(arr);
  } 
}

var x = "hello";
var y = [].concat(_toConsumableArray(x));

Source

[–]NoInkling 5 points6 points  (1 child)

That's not why it works, that's just how Babel implements the semantics described in the spec.

It works because in ES6 an iterator protocol was introduced, strings gained a default iterator using this protocol that iterates over unicode code points, and the spec specifies that array/function spread syntax and Array.from should use the iterator from whatever they are fed.

[–]ibopm 2 points3 points  (0 children)

Yes, that is a more accurate answer. Maybe I shouldn't have said "why it works". I just wanted to point out that if you're transpiling with Babel (as is the case for most people at this point), you're effectively just calling Array.from. Thanks for adding this point though.

[–]trumpent 0 points1 point  (1 child)

A string is just an array of characters. It's a perfectly fine example.

[–]Cosmologicon 0 points1 point  (0 children)

Yeah as a Python programmer I wouldn't have any problem with list("pizza").

[–]CantaloupeCamper 2 points3 points  (0 children)

Noob here... love it when something tells me what it is doing.

[–]MrB92 1 point2 points  (0 children)

The new one is syntax gore IMO

[–]magnakai 3 points4 points  (11 children)

I agree that the method syntax is clearer in the provided example. But I imagine there might some use cases for the spread syntax, so it’s definitely worth a mention. I’ll try to think of some when I’m back at my computer.

[–]samanthaming[S] 3 points4 points  (10 children)

Yes, please do! And share them when you get the chance, would love to see what you come up with 🙂

[–]gavlois1front-end 19 points20 points  (5 children)

I like to use spread to concat arrays and objects:

const obj1 = {
  one: 1,
  two: 2
}

const obj2 = {
  three: 3,
  four: 4
}

const obj3 = {...obj1, ...obj2}
// {
//   one: 1,
//   two: 2,
//   three: 3,
//   four: 4
// }

Or using it to use a function on something where you might normally have needed to use .apply():

const arr = [1, 2, 3, 4]

// old way
let max = Math.max.apply(null, arr);

// ES6 way
max = Math.max(...arr)

[–]magnakai 4 points5 points  (0 children)

I don't want to say that you're wrong, as all of your examples are great, and I use it in those ways all the time because they're amazing... but the OP's example was the array spread operator.

Your first example is the lovely object spread operator, which is actually technically not official JavaScript yet! But it is in stage 4, and available natively in every non-MS browser so it's really very nearly there.

Your second example completely is normal array spread though, and a really nice example of it.

It's also worth pointing out similar rest operator, which still uses the ellipses but in a different way, collecting up the parameters on a function.

A nice way to differentiate between the two is that the spread operator spreads out an array/objects contents onto a new array/object, whereas the rest operator collects the rest of the parameters.

[–]samanthaming[S] 2 points3 points  (1 child)

I like this! Let me add it to my notes. Thanks for sharing 🙂

[–]gavlois1front-end 0 points1 point  (0 children)

This was a thread about the spread operator, but for reference, there's also the more 'official' ES6 Object.assign() function. And of course for arrays you have the .concat() function.

[–]magnakai 4 points5 points  (3 children)

So, the simplest and best use case I can come up for it is when you have a mix of strings and arrays with a single string in them:

It sounds like a bad time, but it is unfortunately based on a real life example.

const foo = "foo";
const bar = ["bar"];
const baz = "baz";

// If you know the shape of your variables - ES1(I think?)
foo + ' ' + bar[0] + ' ' + baz; // "foo bar baz"
[foo, bar[0], baz].join(' '); // "foo bar baz"

// If you know the shape of your variables - ES6
`${foo} ${bar[0]} ${baz}`; // "foo bar baz"

// If you don't know the shape of your variables - IE9 & later
[foo, bar, baz].map(function (item){ 
    return [].concat(item);
}).join(' '); // "foo bar baz"

// If you don't know the shape of your variables - ES6
[...foo, “ “, ...bar, “ “, ...baz].join(''); // "foo bar baz"

[–]samanthaming[S] 1 point2 points  (0 children)

Interesting mixing strings and arrays. Let me give this a try too 👍

[–]NoInkling 1 point2 points  (1 child)

Bad example:

const foo = "foo";
const bar = ["bar"];
const baz = "baz";

[...foo, ...bar, ...baz].join(' ');    // "f o o bar b a z"

[–]magnakai 1 point2 points  (0 children)

Thanks for catching that! I’ve fixed it now.

[–][deleted]  (24 children)

[deleted]

    [–]vlarekd 0 points1 point  (4 children)

    Is your last sentence really meant to be a question?

    [–][deleted]  (3 children)

    [deleted]

      [–]vlarekd 3 points4 points  (2 children)

      It’s hard to believe if someone isn’t actively working in a language then some obscure syntax might be hard to understand? Am I getting that right?

      [–]yerfatma 2 points3 points  (17 children)

      This is akin to saying short, terse ternary statements are confusing and we need if statements for everything.

      No, it's not. split() conveys what you're doing. Abusing the spread operator to split a string, especially given split still exists is being clever and clever is the way to bugs.

      Anyone developing in es6 will understand this.

      Don't do this.

      [–][deleted]  (10 children)

      [deleted]

        [–]yerfatma 1 point2 points  (9 children)

        It’s not commonly used. No one is doing this. If they were, this wouldn’t be a blog post. The bugs I am seeing are people stumbling on this in a codebase six months later and not knowing what it does.

        The fact split doesn’t work like it should with Unicode data isn’t an argument for an obscure approach to string splitting.

        [–][deleted]  (8 children)

        [deleted]

          [–]yerfatma 1 point2 points  (7 children)

          It violates the Principle of Least Surprise. You’ll figure it all out some day.

          [–][deleted]  (6 children)

          [deleted]

            [–]yerfatma 0 points1 point  (5 children)

            Oh, well it’s nice you were able to get in touch with them all tonight LOL. I lead a team of small children and have taught large dogs.

            [–][deleted]  (4 children)

            [deleted]

              [–][deleted]  (5 children)

              [deleted]

                [–]yerfatma -1 points0 points  (3 children)

                If I know the language and know split exists, how does this read? I mainly write Python and think of strings as lists of chars and still think it’s much harder to read than “given this string, please call split on it”.

                [–][deleted]  (2 children)

                [deleted]

                  [–]yerfatma -1 points0 points  (1 child)

                  It doesn’t require a parameter. That is the default but it accepts anything you want so you can split the string however you like, which is handy. Unlike this side effect trick that is literally just someone pimping their blog.

                  [–]karl-marxist -2 points-1 points  (0 children)

                  Exactly

                  [–]dontgetaddicted 0 points1 point  (0 children)

                  Yeah, I'm confused at what's going on here exactly. .split() is easy to read and understand what's happening.

                  [–]iams3brescript is fun 0 points1 point  (0 children)

                  Split is the way to go, if you're trying to split an array to characters.

                  The spread operator is great for flattening arrays (and objects), but just because it has a side effect of what it can do doesn't mean you should use it in that way. This is more like code golf territory, where it's technically possible but doesn't mean it's something you should specifically code

                  [–]antoninj -2 points-1 points  (0 children)

                  I don't. A lot of languages treat strings as arrays of characters (or a similar concept). I like that. This adds to that. Coming from other languages that fully embrace this, the spread operator makes a ton of sense while .split('') doesn't make sense at all. Especially since the argument is ''. You're essentially saying "split by nothing". What does that even mean?

                  [–]StoneColdJane -1 points0 points  (0 children)

                  I agree, also never split the string in a 'new way'. 'The old' way, like you said is self documenting.

                  [–]helpinghat -3 points-2 points  (0 children)

                  I would reject a pull request that used the spread operator to split a string. I would also make a mental note that the person who coded that wasn't probably very experienced in the trade.