DEV Community

Cover image for Spread and Destructuring: A How-To Guide for JavaScripters
Fernando Doglio
Fernando Doglio

Posted on • Edited on

Spread and Destructuring: A How-To Guide for JavaScripters

Get a better understanding of JS spread and destructuring

Most common web-related programming languages have all the basic operators, and by the time they reach a job opportunity, every developer knows how to use them. But there are some operators that aren't that common and not every language has them or if they do, they might not share the same syntax.
This can be a problem for newcomers into a language, trying to read other's code and not having the tools required to map concepts between technologies. In this article, I'm going to talk about 2 of these operators:

  • One that partially due to complex documentation, might be a hard concept to grasp for developers who haven't had a lot of other experience with it. I'm referring to no other than the infamous spread operator.

  • And the other operator is one that represents a very logical action but due to the lack of presence in other languages (Python has it though, and it's one of the many great features of that language), not many devs know about it. I'm talking of course, about destructuring.

Let's dig in!

The Spread Operator

The spread operator (A.K.A those 3 dots you can put before a variable name) is probably the single most misunderstood operator from JavaScript which is a real shame because once you unlock it’s mechanics in your brain, you’ve added a very powerful tool to your arsenal.

Like with everything else, let’s take a quick look at the actual documentation from MDN, which is one of the best sources of JavaScript docs and quick examples:

The Spread syntax allows an iterable such as an array expression or string to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected, or an object expression to be expanded in places where zero or more key-value pairs (for object literals) are expected.

That… that was not really helpful, so let me re-worded a bit:

The Spread syntax allows you to expand something that is currently grouped inside a particular container and assign it to a different container. Compatible containers include: arrays, strings, objects and any iterable (such as Maps, Sets, TypedArrays, etc) and their elements can be expanded into function arguments, array elements and key-value pairs

Was that better? Now with that second definition in mind, let me show you some quick examples and hopefully, cement these new mechanics in your, now expanded, programming mind.


let myArray1 = [1,2,3]
let myString = "Hey planet!"
let myObject = {
    name: "Fernando Doglio",
    age: 35,
    country: "Uruguay",
    [Symbol.iterator]: function* () { //we're making the object iterable so we can spread it
        yield myObject.name
        yield myObject.age
        yield myObject.country
    }
}

function test() {
    console.log(arguments)
}

let splitLetters = [...myString] //no longer need for myString.split()
console.log(splitLetters)
//[ 'H', 'e', 'y', ' ', 'p', 'l', 'a', 'n', 'e', 't', '!' ]

let objLetters = {...myString}
console.log(objLetters)
/*
{ '0': 'H',
  '1': 'e',
  '2': 'y',
  '3': ' ',
  '4': 'p',
  '5': 'l',
  '6': 'a',
  '7': 'n',
  '8': 'e',
  '9': 't',
  '10': '!' }
*/
test(...myString)
/*
[Arguments] {
  '0': 'H',
  '1': 'e',
  '2': 'y',
  '3': ' ',
  '4': 'p',
  '5': 'l',
  '6': 'a',
  '7': 'n',
  '8': 'e',
  '9': 't',
  '10': '!' }
*/
//the same thing
test.call(null, ...myArray1)
//[Arguments] { '0': 1, '1': 2, '2': 3 }
test.apply(null, myArray1)
//[Arguments] { '0': 1, '1': 2, '2': 3 }

let objValues = [...myObject] //if your object is iterable, this can substitute Object.values(myObject)
console.log(objValues)
//[ 'Fernando Doglio', 35, 'Uruguay' ]

let {name, age} = {...myObject} //spread properties into individual variables
console.log("Name::", name, " age::", age)
//Name:: Fernando Doglio  age:: 35


test(...myObject) //we've turned our object into 3 different arguments
//[Arguments] { '0': 'Fernando Doglio', '1': 35, '2': 'Uruguay' }

Take a moment to read through the examples and their respective outputs, I’ll be here.

All done now? OK, let’s review then. Although some of these examples are nothing but fancy tricks the operator allows us to perform, there are some interesting bits we can get from such a basic set of examples:

  • By either surrounding our container with {}, [] or () we’re specifying the desired target (i.e. we’re either spreading into a new object, a new array or an argument list).

  • Spreading a String provides allows us to split it by character, which is something we’ve always done with string.split() . With the added benefit that we can decide if we want the result of the split in array, object or arguments format.

  • Spreading an array as part of the Function.call method call, nullifies the need for the Function.apply method. **Bonus tip*: simply spreading the array as part of the normal function call, nullifies the need for both of them.

  • To play around with my custom object, I did had to make it *iterable . *Although not a big issue, you need to remember that, otherwise the operator won’t work on most of the cases.

Let me now quickly show you a set of more advanced and arguably useful things we can achieve with the spread operator:



let array1 = [1,2,3,4]

//Copying an array
let copyArray = [...array1]
copyArray.push(4)
console.log(array1)
console.log(copyArray)
/*
[ 1, 2, 3, 4 ]
[ 1, 2, 3, 4, 4 ]
*/

//**WARNING*/
let otherArray = [[1], [2], [3]]
copyArray = [...otherArray]
copyArray[0][0] = 3
console.log(otherArray)
console.log(copyArray)
/*
Spread does a shallow copy
[ [ 3 ], [ 2 ], [ 3 ] ]
[ [ 3 ], [ 2 ], [ 3 ] ]
*/

//Array concats
let array2 = ['a', 'b', 'c']

let result = [...array1, ...array2]
console.log(result)
//[ 1, 2, 3, 4, 'a', 'b', 'c' ]


//**WARNING */
let myString = "hello world"
let result2 = [...array1, ...myString] //totally valid but...
console.log(result2)
//[ 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' ]

result2 = array1.concat(myString) //maybe this is what you really want
console.log(result2)
//[ 1, 2, 3, 4, 'hello world' ]

result2 = [...array1, ...array2, myString] //or this is valid too
console.log(result2)
//[ 1, 2, 3, 4, 'a', 'b', 'c', 'hello world' ]


//Merging objects
let myObj1 = {
    name: "Fernando Doglio",
    age: 34
}

let myObj2 = {
    name: "Fernando Doglio",
    age: 35,
    country: "Uruguay"
}

let mergedObj = {...myObj1, ...myObj2}
console.log(mergedObj)
// { name: 'Fernando Doglio', age: 35, country: 'Uruguay' }


//Cleaning up repeated elements from an array
let myArray3 = [1,2,3,4,4,4,4,2,3,3,4,6]
let mySet = new Set(myArray3)
myArray3 = [...mySet]
console.log(myArray3)
//[ 1, 2, 3, 4, 6 ]

view rawadvanced-spread-examples.js hosted with  by GitHub

Some highlights:

  • Cloning an array is simple, but it’s a shallow copy, you can see in the example above, how a multidimensional array is not fully cloned. So be careful when using this shortcut.

  • Merging arrays is very powerful too. There is a caveat though, just try not to directly substitute the concat method call with the spread operator, since they behave differently with their values. That being said, the spread version of array concatenation (when done right) is a lot more declarative than the method call version.

  • Merging objects is trivial now. Whilst before you’d have to perform some kind of loop, taking into account keys on one side and values on the other. That is no longer needed, now with a single line of code, you can merge several objects into one. Take into account that when there is a key collision, right-most objects will overwrite the previous value.

  • Finally, cleaning up repeated elements from an array is finally as easy as it should’ve been from the start. When Set was added to the language, we all cried tears of joy (well, at least I know I did!). But when realizing that the Set.values method didn’t return a plane array, I wanted to cry again, but for a whole different reason. Now I no longer need to iterate over that result, I can simply spread the set into an array and forget about it.

That is it for the spread operator, I hope the above examples have given you enough meat to start chewing. I’ll try to expand your mind a bit now with destructuring and what that means for your syntax and your code.

Destructuring

Another interesting new feature from JavaScript (and may I say, one I loved from Python when I first encountered it) is *destructuring. *This syntax allows us to *unpack *values from objects and arrays into individual properties. By itself, destructuring is amazing, but we can also mix it up with the spread operator and we’ll get some interesting results.

Languages such as Perl or Python make a big deal out of their list-based features because let’s face it, they’re very powerful. Who hasn’t felt amazing when doing something like:

    a = 1
    b = 2
    a, b = b, a

How many times did you wish you could do that with JavaScript? And what about returning more than one value from a function? That always meant you had to return either an array or an object with the values packed inside it, and of course, treat them accordingly afterward.

There is basically no easy way for you to write a generic function that returns multiple values without making some compromises at the syntactic or semantic level (either add extra code to handle that or let your developer know you’re returning a bunch of data and have them deal with it however they like).

*Destructuring *adds a solution to all of that and more, the syntax is quite simple, let me show you:

    //swapping values
    let a = 1
    let b = 2
    [a, b] = [b, a]

    //multiple returned values
    function fn() {
      return [1,2,4]
    }
    [a,b,c] = fn()
    /*
    a = 1
    b = 2
    c = 4
    */

Basically, with the array notation, we can unpack whatever values we have on the right side and assign them on the left. What’s even more, what if you wanted to get the first two values from your array and the rest be added onto another list? Easy!

    let myList = [1,2,3,4,5,6,7]

    [first, second, ...tail] = myList
    /*
    first = 1
    second = 2
    tail = [3,4,5,6,7]
    */

As you can see, performing multiple assignments is quite straightforward. This is helpful especially when dealing with multi-group regular expressions, like:

    function parseURL(url) { 
      var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
      if (!parsedURL) {
        return [];
      }
      [, ...parsedValues] =  parsedURL // we're ignoring the first element
        return parsedValues.map( v => v.length ? v : undefined) //We're making sure empty matches are set to undefined
    }

    [protocol, host, path] = parseURL("[https://www.fdoglio.com/blog](https://www.fdoglio.com/blog)")
    console.log(`The host is -${host}-, the protocol -${protocol}- and you're accessing the path -${path}-`);

The above example is using destructuring in two places:

  1. Initially inside the function, to remove the first element of the matches array. This could be done with parsedURL.shift() too, but then again, we’re going for a declarative approach here.

  2. To assign the returned values into multiple, individual variables, so you can treat them however you like. In our case, we’re just individually using them on the template string.

You can even set default values in case the right-hand side is undefined.

    [protocol, host, path="none"] = parseURL("[https://www.fdoglio.com/](https://www.fdoglio.com/)");
    console.log(`The host is -${host}-, the protocol -${protocol}- and you're accessing the path -${path}-`);

    //The host is -[www.fdoglio.com-](http://www.fdoglio.com-), the protocol -https- and you're accessing the path -none-

Note that this works, because we’re manually changing empty matches into undefined in our parsing function, otherwise,°° the default values would be ignored.

By the same standards, we can have named attributes passed on to functions and even default values during function calls, like this:

    let myObject = {
        name: "Fernando Doglio",
        country: "Uruguay",
        age: 35
    }

    //destructuring
    function wishHappyBirthday({name, age, numberOfKids=2}) {
        console.log(`Hello ${name} happy ${age}th birthday, have a great day with your wife and ${numberOfKids} kids`)
    }

    wishHappyBirthday(myObject) //expands my object into the actual function parameters

In that example, we’re doing everything we’ve been doing with arrays, but with objects including only pulling the properties we want and setting default values in case they don’t exist.

Make sure that you’re using the correct names on the function declaration to match the property names since assignment is not done through order matching (like with normal functions), but rather by name matching.

You can also do the above but pull a set of particular keys into individual variables, like this:

    const student = {
        firstname: 'Fernando',
        lastname: 'Doglio',
        country: 'Uruguay'
    };

    //pull properties by name
    let { firstname, lastname } = student
    console.log(`Nice to meet you ${firstname} ${lastname}!`)

    //assign properties to particular variable names
    let { firstname: primerNombre, lastname: apellido} = student
    console.log(primerNombre, apellido);

The first example is quite straightforward, simply pulling two particular properties from the object, leaving aside country . But on the second example, I’m also showing you how to reassign the content of a property into a particular new variable (in case the name’s already taken or you need to have more control over it).

Final thoughts

Both, destructuring and the spread operator have been part of the language for a little while now, but adoption hasn’t been that high. My take on it is that these are foreign concepts for developers who started and have always worked on JavaScript alone and with this articles (and others like it) my hope is that you’ll be able to start switching your coding style into a more declarative approach and accept these new tools the language is giving you.

If you are one of the crazy ones who’s been playing around with these since day one, maybe leave a comment below sharing your craziest use of destructuring or your thoughts on the spread operator! I’d love to know what you’re all doing with these two!

See you on the next one!

Top comments (4)

Collapse
 
flrnd profile image
Florian Rand

Hey Fernando, would be nice if you share the full article.

Collapse
 
deleteman123 profile image
Fernando Doglio

The article is shared! Just click on the link, it's free to read. I'll try to get approval from the publication to post the entire thing here. If I do, I'll update it.

Collapse
 
flrnd profile image
Florian Rand

I hope so! I really liked the full read ;)

Thread Thread
 
deleteman123 profile image
Fernando Doglio

Took a while, but the article is now fully published!