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:
- Write a
merge function that accepts any number of arrays and returns a single merged array
- Create a
filterObject function that removes specified keys from an object
- 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+!