JS Spread syntax ...

·

4 min read

Spread syntax (...)

... allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected. In an object literal, the spread syntax enumerates the properties of an object and adds the key-value pairs to the object being created.

As function argument:

myFn (arg1, arg2, ...arg3, arg4) // arg3 is an iterable object (arrays, strings)

let arr9 = [1, 3, 5]
function sum(n1, n2, n3) {
    return n1 + n2 + n3
}

sum(...arr9) // 9
sum.apply(null, arr9) // 9

*Be ware of the risk of exceeding the JavaScript engine's argument length limit!

Maybe it's just me but it suddenly(finally?) made sense to me why Math.max(arrOfNumbers) doesn't work but Math.max(...arrOfNumbers) does:

let numArr = [3, 5, 7, 19]
console.log(Math.max(numArr)) // NaN
console.log(Math.max(...numArr)) // 19
let numArr2 = [70, 100]
console.log(Math.max(...numArr, ...numArr2)) // 100

Array literals:

[item1, item2, ...item3, item4] // item3 is an iterable object (arrays, strings)

let shortArr = [4, 5, 6]
let longArr = [1, 2, 3, ...shortArr, 7, 8]
console.log(longArr)  // [1, 2, 3, 4, 5, 6, 7, 8]

Object literals:

{key: value, ...obj, key: value, key: value}

let arr = [1, 3, 5, 7]
let obj1 = {...arr}
let obj2 = {'x':1, ...arr, 'z':100}
let obj3 = {'c': 9, ...obj1, 'a': 8, 'b': 106} 
console.log(obj1) // {0: 1, 1: 3, 2: 5, 3: 7}
console.log(obj2) // {0: 1, 1: 3, 2: 5, 3: 7, x: 1, z: 100}
console.log(obj3) // {0: 1, 1: 3, 2: 5, 3: 7, c: 9, a: 8, b: 106}

*Note that the key:value pairs got "reordered"--the iterable key:value pairs were placed before all other pairs.

String

let str = 'abc'
console.log(...str) // a b c
let arr = [...str]
console.log(arr) // ['a', 'b', 'c']

*Although, I don't find the spread syntax particularly useful to strings (yet).

Superpower of the spread syntax

Works with 'new' operator when calling a constructor

const dateFields = [2022, 0, 1];  // [year, month, date]
const d = new Date(...dateFields);
console.log(d) // Sat Jan 01 2022 00:00:00 GMT-0800 (Pacific Standard Time)

*Can't use apply() to construct a function

Create a copy of an array

let arr = [2, 4, 5, 0]
let copyOfArr = [...arr] // another way of creating a copy is arr.slice()
copyOfArr.sort((a, b) => a - b)
console.log(arr) // [2, 4, 5, 0]
console.log(copyOfArr) // [0, 2, 4, 5]

Create new array using an existing array or concatenating arrays

let arr1 = [2, 3]
let arr2 = [1, ...arr1, 4]
console.log(arr2) // [1, 2, 3, 4]
let arr3 = [5, 6]
let arr4 = [...arr2, ...arr3]
console.log(arr5) // [1, 2, 3, 4, 5, 6]

Create an array filled with integers

let nums = [...Array(10).keys()] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Clone or merge objects

const obj1 = { z: 'bar', x: 42 }
const obj2 = { z: 'baz', y: 13 }

const clonedObj = { ...obj1 }
console.log(clonedObj) // {z: 'bar', x: 42}

const mergedObj = { ...obj1, ...obj2 }
console.log(mergedObj) // {z: 'baz', x: 42, y: 13}

*Note that spread syntax doesn't mutate an object (need to use Object.assign(obj, {key: newValue}):

const obj1 = { z: 'bar', x: 42 }
Object.assign(obj1, { x: 1337 })
console.log(obj1); // Object { z: "bar", x: 1337 }
const obj1 = { z: 'bar', x: 42 }
const obj2 = { z: 'baz', y: 13 }

const merge = (...objects) => ({ ...objects })
const mergedObj = merge(obj1, obj2)

console.log(mergedObj) // { 0: { z: 'bar', x: 42 }, 1: { z: 'baz', y: 13 } }

*As shown above, the spread syntax spreads an array of arguments into the object literal. To do a "real" merge, rewrite the merge function using reduce():

const obj1 = { z: 'bar', x: 42 }
const obj2 = { z: 'baz', y: 13 }
const mergeObjects = (...objects) => objects.reduce((acc, cur) => ({ ...acc, ...cur })) // note here objects are an array
const mergedObject = mergeObjects(obj1, obj2)
console.log(mergedObject) // { z: 'baz', x: 42, y: 13 }

Personally, I found the function expression above confusing, so I rewrote the function below with commented out console.logs that helped me understand the code:

let obj1 = {a: 2, b: 5, c: 7}
let obj2 = {a: 2, d: 4}

function mergeFn(...objects) {
 // console.log(...objects) 
 return objects.reduce((acc, cur) => {
  // console.log(acc)
  // console.log(cur)
  // console.log({...acc, ...cur})
  return {...acc, ...cur}
 }, {})
}

let result = mergeFn(obj1, obj2) 
console.log(result) // {a: 2, b: 5, c: 7, d: 4}

Well, that's all for now. With great power comes great responsibility risk, but I can't wait to try using the spread syntax more when solving coding challenges, even if that means more bugs and errors.