Apps Script V8 adds destructuring from  JavaScript ES6. Legacy Apps Script already had destructuring of arrays added fairly recently, but v8 gives full a destructuring capability.

Destructuring: What is it?

It’s a way of plucking elements or property values from arrays or objects, assigning them to individual variables so they can be handled more easily and concisely.

Deconstructing arrays

Legacy Apps Script already allows you to do a subset of this, where elements are positionally extracted from an array – in this case the kind of data you’d get back from a getValues() on a sheet

var yourFunction = function () {   
  const values = [['first','last'],['john','smith'],['jane','doe']];
  const [headers,john,jane] = values
  // [first, last]
  Logger.log(headers)
  // [john, smith]
  Logger.log(john)
  // [jane, doe]
  Logger.log(jane)
}

With V8, you also get the  …rest syntax which allows you to assign everything else in the array that you haven’t specifically assigned to other variables.

const rarray = () => {
  const values = [['first','last'],['john','smith'],['jane','doe']];
  const [headers,...data] = values
  Logger.log(headers)
  // [[john, smith], [jane, doe]]
  Logger.log(data)
}

That’s equivalent (but rather more meaningful) to something like this that you may have done in legacy Apps Script

function larray () {
  var values = [['first','last'],['john','smith'],['jane','doe']];
  var headers = values.slice(0,1)[0]
  var data = values.slice(1)
  // [first, last]
  Logger.log(headers)
  // [[john, smith], [jane, doe]]
  Logger.log(data)
}

let and const

These behave exactly as normal, so

  const [headers,...data] = values
  // this would be an error
  const headers = 'something else'
  // as would this
  headers = 'another thing'

  let [headers,...data] = values
  // this would be an error
  let headers = 'something else'
  // this would be ok
  headers = 'another thing'

Destructuring objects

Completely new in V8 is the ability to deconstruct objects. You can declare new variables the same as the property names of the object you want to extract from.

const dob = () => {
  const john = { 
    firstName:'John',
    middleName: 'Eustace', 
    lastName:'Smith',
  }
  const { firstName, lastName } = john
  // John Smith
  Logger.log(firstName + ' ' + lastName)
}

Assigning alternative names

The variables you declare, by default, have the same name as the property from which they were extracted, but you can change that too.

const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }];
  // deconstruct the array
  const [john, jane] = data
  // provide alternative variable names
  const { firstName: johnFirst, lastName: johnLast } = john
  const { firstName: janeFirst, lastName: janeLast } = jane
  // John Smith
  Logger.log(johnFirst + ' ' + johnLast)  
  // Jane Doe
  Logger.log(janeFirst + ' ' + janeLast)
}

Default values

Newly declared variables can have default values in both array and object destructuring. This examples combines all those possibilities.

const dobd = () => {
  const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }];
  // deconstruct the array
  const [john, jane, bob = {firstName:'captain', lastName:'anonymous'}] = data
  // provide alternative variable names
  const { firstName: johnFirst, lastName: johnLast, middleName: johnMiddle = "thefirst" } = john
  const { firstName: janeFirst, lastName: janeLast, middleName: janeMiddle = "thesecond"  } = jane
  const { firstName: bobFirst, lastName: bobLast, middleName: bobMiddle = "thebob"  } = bob
  // John Eustace Smith
  Logger.log(johnFirst + ' ' + johnMiddle + ' ' +johnLast)  
  // Jane thesecond Doe
  Logger.log(janeFirst + ' ' + janeMiddle + ' '  + janeLast)
  // captain thebob anonymous
  Logger.log(bobFirst + ' ' + bobMiddle + ' '  + bobLast)
}

object …rest

Just as with arrays, you can use …rest with objects.

const dobe = () => {
  const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }];
  // deconstruct the array
  const [john, jane] = data
  // pull out one item, and keep the rest as an object
  const {lastName,...forenames} = john
  // Smith
  Logger.log(lastName)
  // {middleName=Eustace, firstName=John}
  Logger.log(forenames)
}

Skipping

You may not want every element of an array, so you can simply miss out variable names for the elements you don’t want.

const dobf = () => {
  const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }, {
    firstName:'Bob', 
    lastName:'Green' 
  }];
  // just want the 3rd element
  const [,,bob] = data
  // pull out one item, and keep the rest as an object
  const {lastName,...forenames} = bob
  // Green
  Logger.log(lastName)
  // {firstName=Bob}
  Logger.log(forenames)
}

Combining destructuring arrays and objects

All of those things can be combined, like this

const dobg = () => {
  const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }, {
    firstName:'Bob', 
    lastName:'Green' 
  }];
  // just want the firstName and middlename property of the 3rd element
  const [,,{firstName: whoIsBob, middleName: bobMiddle = "the bob has no middle"}] = data
  // Bob the bob has no middle
  Logger.log(whoIsBob + ' ' + bobMiddle)

}

Array arguments and destructuring

One of the big wins for destructuring is the role it plays in decluttering array arguments. Now the arguments themselves can be destructured.

For all these examples, I’m using the usual data below, which I won’t bother repeating from now on.

   const data = [{ 
    firstName:'John', 
    middleName: 'Eustace', 
    lastName:'Smith',
  },{
    firstName:'Jane', 
    lastName:'Doe' 
  }, {
    firstName:'Bob', 
    lastName:'Green' 
  }];

Passing as a plain argument.

This passes the thing that needs to be logged to the logger and is simple when only one or a few arguments.

  const loga = (firstName) => {
    Logger.log(firstName)
  }
  // passes the variable to be logged
  // John, Jane, Bob
  data.forEach(f=>loga(f.firstName))

Passing as an undestructured object

Another alternative is to pass the object, and the logger knows which property to extract

  const logb = (ob) => {
    Logger.log(ob.firstName)
  }
  // passes the variable to be logged
  // John, Jane, Bob
  data.forEach(f=>logb)
  // or even more concisely
  data.forEach(logb)

Destructing in the argument list

In this model, everything discussed destructuring (including renaming, defaulting, skipping) above can be achieved in the argument list of the receiving function.

  const logc = ({ firstName }) => {
    Logger.log(firstName)
  }
  // John, Jane, Bob
  data.forEach(logc)

This may not seem huge with only a few arguments, but one of the common errors in functions with many arguments is getting them in the wrong order or having to provide values for optional arguments. With destructuring the order is irrelevant, and you only need to pick out the values that are required.

Variable number of arguments

Dealing with an unknown number of variables in functions has always been a bit of a hack. A function declared with function has a special variable called arguments available for use, which is an array-like object which can be interrogated to figure out what arguments were passed. Before you can use it though, you have to convert it into a genuine array – which we can by using the slice method from the Array prototype – as below. Once that’s done, we can proceed normally, treating the argument list as an array.

  const logc = ({ firstName }) => {
    Logger.log(firstName)
  }
  function loge  ()  {
    const args = Array.prototype.slice.apply(arguments)
    args.forEach(logc)
  }
  const [john,,bob] = data
  loge(john,bob)

The arguments variable doesn’t exist in arrow functions, but there is something better. Spread syntax.

  const logc = ({ firstName }) => {
    Logger.log(firstName)
  }
  const logd = (...args) => {
    args.forEach(logc)
  }
  const [john,,bob] = data
  loge(john,bob)

Spread syntax

We’ve covered some of the ideas of spread syntax already (it’s like …rest but in reverse), but let’s look a little deeper. Just as in destructuring, spread syntax applies to both arrays and object literals.

Copying an array

These are all equivalent

  const c1 = data.slice()
  const c2 = data.map(f=>f)
  const c3 = [...data]
  Logger.log(c1)
  Logger.log(c2)
  Logger.log(c3)

As are these

  const c4 = c1.concat(c2,c3)
  const c5 = [...c1,...c2,...c3]

Copying an object literal

const otherJane = {...jane}

Copying and replacing properties

  const betterJane = {...jane,firstName: 'Better Jane',salution: 'Ms'}
  // {lastName=Doe, salution=Ms, firstName=Better Jane}
  Logger.log(betterJane)

Note that spreading doesn’t make a deep copy of the object. Consider this example

  const cob = {
      firstName:'John', 
      lastName:'Doe',
      relatedTo: [{
        person: jane,
        relationship: 'brother'
      }]
    }
  const doe = {...cob}
  console.log(JSON.stringify(doe))

Gives

{
	"firstName": "John",
	"lastName": "Doe",
	"relatedTo": [{
		"person": {
			"firstName": "Jane",
			"lastName": "Doe"
		},
		"relationship": "brother"
	}]
}

If you then amend jane the difference will be reflected in the doe object

  jane.middleName = 'adding a middle name'
  console.log(JSON.stringify(doe))

Like this

{
	"firstName": "John",
	"lastName": "Doe",
	"relatedTo": [{
		"person": {
			"firstName": "Jane",
			"lastName": "Doe",
			"middleName": "adding a middle name"
		},
		"relationship": "brother"
	}]
}

So spreading will only copy pointers to other objects, not clone the object themselves.

Summary

More great cleaning up of JavaScript courtesy of ES6. These destructuring and spreading capabilities, which at first may again seem a little like syntactic sugar, have contributed greatly to  the development of state management frameworks such as Vuex and Redux for client side apps. V8 brings some of that cleanliness to Apps Script.