Advanced JavaScript (ES6+)

Spread and Rest Operators

13 min Lesson 6 of 40

Spread and Rest Operators

The spread (...) and rest operators are powerful ES6+ features that use the same syntax but serve different purposes. They make working with arrays, objects, and function parameters much more elegant and efficient.

The Spread Operator for Arrays

The spread operator expands an array into individual elements. It's incredibly useful for copying, combining, and manipulating arrays:

// Copying arrays (shallow copy) const original = [1, 2, 3]; const copy = [...original]; console.log(copy); // [1, 2, 3] // Combining arrays const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; const combined = [...arr1, ...arr2]; console.log(combined); // [1, 2, 3, 4, 5, 6] // Adding elements while spreading const numbers = [2, 3, 4]; const extended = [1, ...numbers, 5]; console.log(extended); // [1, 2, 3, 4, 5]
Tip: The spread operator creates a shallow copy. For nested arrays or objects, the nested references are copied, not the nested values themselves.

Spread Operator with Function Arguments

You can use spread to pass array elements as individual function arguments:

function add(a, b, c) { return a + b + c; } const numbers = [1, 2, 3]; // Old way console.log(add.apply(null, numbers)); // 6 // ES6+ way with spread console.log(add(...numbers)); // 6 // Math functions const scores = [85, 92, 78, 95, 88]; console.log(Math.max(...scores)); // 95 console.log(Math.min(...scores)); // 78

The Spread Operator for Objects

ES2018 introduced object spread, allowing you to copy and merge objects easily:

// Copying objects (shallow copy) const person = { name: 'John', age: 30 }; const personCopy = { ...person }; console.log(personCopy); // { name: 'John', age: 30 } // Merging objects const defaults = { theme: 'light', language: 'en' }; const userSettings = { language: 'ar', fontSize: 16 }; const settings = { ...defaults, ...userSettings }; console.log(settings); // { theme: 'light', language: 'ar', fontSize: 16 } // Adding/overriding properties const user = { name: 'Alice', role: 'user' }; const admin = { ...user, role: 'admin', permissions: ['read', 'write'] }; console.log(admin); // { name: 'Alice', role: 'admin', permissions: ['read', 'write'] }
Order Matters: When merging objects, properties from later objects override earlier ones. In the example above, language: 'ar' overrides language: 'en'.

Rest Parameters in Functions

The rest operator (...) collects multiple elements into an array. When used in function parameters, it's called "rest parameters":

// Collecting remaining arguments function sum(...numbers) { return numbers.reduce((total, num) => total + num, 0); } console.log(sum(1, 2, 3)); // 6 console.log(sum(1, 2, 3, 4, 5)); // 15 // Combining regular parameters with rest function introduce(greeting, ...names) { return `${greeting}, ${names.join(' and ')}!`; } console.log(introduce('Hello', 'Alice')); // "Hello, Alice!" console.log(introduce('Hello', 'Alice', 'Bob', 'Charlie')); // "Hello, Alice and Bob and Charlie!"
Important: Rest parameters must be the last parameter in a function. You cannot have parameters after a rest parameter.

Rest in Destructuring

Rest can also be used in destructuring to collect remaining elements:

// Array destructuring with rest const [first, second, ...rest] = [1, 2, 3, 4, 5]; console.log(first); // 1 console.log(second); // 2 console.log(rest); // [3, 4, 5] // Object destructuring with rest const person = { name: 'John', age: 30, city: 'New York', country: 'USA' }; const { name, ...details } = person; console.log(name); // "John" console.log(details); // { age: 30, city: 'New York', country: 'USA' }

Spread vs Rest: Understanding the Difference

Spread (expands): - Used where multiple values are expected - In function calls: func(...arr) - In array literals: [...arr1, ...arr2] - In object literals: {...obj1, ...obj2} Rest (collects): - Used where multiple values need to be collected into one - In function parameters: function(...args) - In destructuring: const [a, ...rest] = array

Cloning vs Shallow Copying

// Shallow copy - nested objects share references const original = { name: 'John', address: { city: 'New York' } }; const copy = { ...original }; copy.address.city = 'Boston'; console.log(original.address.city); // "Boston" (affected!) // Deep copy solution const deepCopy = JSON.parse(JSON.stringify(original)); // Or use a library like lodash's cloneDeep

Practical Use Cases

// 1. Converting NodeList to Array const divs = document.querySelectorAll('div'); const divArray = [...divs]; // 2. Removing duplicates from array const numbers = [1, 2, 2, 3, 3, 4]; const unique = [...new Set(numbers)]; console.log(unique); // [1, 2, 3, 4] // 3. Conditional spreading const isAdmin = true; const user = { name: 'Alice', ...(isAdmin && { role: 'admin', permissions: ['all'] }) }; // 4. Immutable array operations const todos = ['Task 1', 'Task 2']; const addTodo = (todos, newTodo) => [...todos, newTodo]; const removeTodo = (todos, index) => [ ...todos.slice(0, index), ...todos.slice(index + 1) ];

Common Patterns

// Prepending and appending const arr = [2, 3, 4]; const prepended = [1, ...arr]; // [1, 2, 3, 4] const appended = [...arr, 5]; // [2, 3, 4, 5] // Inserting in the middle const insert = (arr, index, item) => [ ...arr.slice(0, index), item, ...arr.slice(index) ]; // Merging with precedence const config = { ...defaultConfig, ...userConfig, ...overrides }; // Function with optional parameters function createUser(name, options = {}) { return { name, role: 'user', active: true, ...options // Override defaults }; }

Practice Exercise:

Task: Create functions using spread and rest operators:

  1. Write a merge function that accepts any number of arrays and returns a single merged array
  2. Create a filterObject function that removes specified keys from an object
  3. Build a logger function that accepts a log level and any number of messages

Solution:

// 1. Merge arrays function merge(...arrays) { return [].concat(...arrays); } console.log(merge([1, 2], [3, 4], [5])); // [1, 2, 3, 4, 5] // 2. Filter object function filterObject(obj, ...keysToRemove) { const filtered = { ...obj }; keysToRemove.forEach(key => delete filtered[key]); return filtered; } const user = { name: 'John', age: 30, password: 'secret' }; console.log(filterObject(user, 'password')); // { name: 'John', age: 30 } // 3. Logger function logger(level, ...messages) { const timestamp = new Date().toISOString(); console.log(`[${timestamp}] ${level.toUpperCase()}:`, ...messages); } logger('info', 'User logged in', 'ID: 123');

Performance Considerations

Note: While spread is convenient, be mindful of performance with large arrays or objects. For very large datasets or frequent operations, consider alternatives like Array.concat() or Object.assign().

Summary

In this lesson, you learned:

  • Spread operator expands arrays and objects into individual elements
  • Rest parameters collect multiple arguments into an array
  • Spread creates shallow copies of arrays and objects
  • Rest must be the last parameter in destructuring or functions
  • Both operators use the same ... syntax but serve opposite purposes
  • Practical applications include merging, copying, and function parameters
Next Up: In the next lesson, we'll explore Enhanced Object Literals and how they simplify object creation in ES6+!

ES
Edrees Salih
12 hours ago

We are still cooking the magic in the way!