JavaScript Rest Spread
JavaScript's rest and spread operators are powerful features introduced in ES6 (ECMAScript 2015) that make working with arrays and objects much more convenient. Both use the same ...
syntax but serve different purposes depending on the context.
Introduction
The rest and spread operators might look identical (...
), but they do opposite things:
- Spread operator: Expands elements from an array or properties from an object
- Rest operator: Collects multiple elements into an array or object properties
Let's explore both features and see how they can make your JavaScript code more elegant and expressive.
The Spread Operator
The spread operator (...
) allows you to expand an iterable (like an array) into individual elements, or an object into individual key-value pairs.
Spreading Arrays
Basic Array Spreading
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5];
console.log(newNumbers); // Output: [1, 2, 3, 4, 5]
In this example, we've expanded the numbers
array and added more elements to create a new array.
Combining Arrays
const fruits = ['apple', 'banana'];
const vegetables = ['carrot', 'tomato'];
const produce = [...fruits, ...vegetables];
console.log(produce); // Output: ['apple', 'banana', 'carrot', 'tomato']
Copying Arrays
const original = [1, 2, 3];
const copy = [...original];
copy.push(4);
console.log(original); // Output: [1, 2, 3]
console.log(copy); // Output: [1, 2, 3, 4]
This creates a shallow copy of the original array.
Spreading Objects
Copying and Adding Properties
const person = { name: 'John', age: 30 };
const extendedPerson = {
...person,
job: 'Developer',
location: 'New York'
};
console.log(extendedPerson);
// Output: { name: 'John', age: 30, job: 'Developer', location: 'New York' }
Merging Objects
const defaults = { theme: 'dark', fontSize: 16 };
const userSettings = { fontSize: 20 };
const settings = { ...defaults, ...userSettings };
console.log(settings);
// Output: { theme: 'dark', fontSize: 20 }
Note that when properties overlap, the last object's properties will override earlier ones.
Function Arguments
You can use the spread operator to pass array elements as separate arguments:
function sum(x, y, z) {
return x + y + z;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // Output: 6
The Rest Operator
The rest operator (...
) collects multiple elements into an array or object. It's essentially the opposite of the spread operator.
Rest with Arrays
Collecting Remaining Elements
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // Output: 1
console.log(second); // Output: 2
console.log(rest); // Output: [3, 4, 5]
Rest with Function Parameters
The rest operator allows you to represent an indefinite number of arguments as an array:
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2)); // Output: 3
console.log(sum(1, 2, 3, 4)); // Output: 10
Combining Regular Parameters with Rest
function displayInfo(name, age, ...hobbies) {
console.log(`Name: ${name}`);
console.log(`Age: ${age}`);
console.log(`Hobbies: ${hobbies.join(', ')}`);
}
displayInfo('Jane', 25, 'reading', 'swimming', 'coding');
// Output:
// Name: Jane
// Age: 25
// Hobbies: reading, swimming, coding
Rest with Objects
You can also use the rest operator with object destructuring:
const { name, age, ...rest } = {
name: 'Alice',
age: 28,
job: 'Engineer',
location: 'London',
hobbies: ['reading', 'hiking']
};
console.log(name); // Output: Alice
console.log(age); // Output: 28
console.log(rest); // Output: { job: 'Engineer', location: 'London', hobbies: ['reading', 'hiking'] }
Real-World Applications
Function with Default Options
function createUser(username, { age = 18, role = 'user', ...otherInfo } = {}) {
return {
username,
age,
role,
...otherInfo,
createdAt: new Date()
};
}
const user1 = createUser('john_doe', { age: 25, role: 'admin', email: '[email protected]' });
console.log(user1);
// Output:
// {
// username: 'john_doe',
// age: 25,
// role: 'admin',
// email: '[email protected]',
// createdAt: [Date object]
// }
const user2 = createUser('jane_doe');
console.log(user2);
// Output:
// {
// username: 'jane_doe',
// age: 18,
// role: 'user',
// createdAt: [Date object]
// }
State Management in React
While this is a React example, it illustrates a common use case for the spread operator:
// Updating state in React
function reducer(state, action) {
switch (action.type) {
case 'UPDATE_USER':
return {
...state,
user: {
...state.user,
...action.payload
}
};
// other cases...
default:
return state;
}
}
Dynamic Function Arguments
function logData(method, ...data) {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${method}]:`, ...data);
}
logData('INFO', 'User logged in', { userId: 123 });
// Output: [2023-09-15T12:00:00.000Z] [INFO]: User logged in { userId: 123 }
Common Gotchas and Tips
Rest Must Be the Last Parameter
When using rest parameters in function definitions, the rest parameter must be the last parameter:
// ✅ Correct
function correct(a, b, ...rest) {}
// ❌ Incorrect - will cause a SyntaxError
// function incorrect(...rest, a, b) {}
Spread Creates Shallow Copies
The spread operator only creates a shallow copy, which means that nested objects or arrays are still references to the original:
const original = {
name: 'John',
skills: ['JavaScript', 'React']
};
const copy = { ...original };
copy.skills.push('Node.js');
console.log(original.skills); // Output: ['JavaScript', 'React', 'Node.js']
console.log(copy.skills); // Output: ['JavaScript', 'React', 'Node.js']
To create a deep copy, you'd need to handle nested structures manually or use libraries like lodash's cloneDeep
.
Summary
The rest and spread operators are powerful features in modern JavaScript:
- Spread (
...
) expands iterables like arrays into individual elements or objects into individual properties - Rest (
...
) collects multiple elements into an array or multiple properties into an object
These operators help you write more concise and expressive code, particularly when:
- Creating copies of arrays and objects
- Combining arrays or objects
- Working with variable numbers of function parameters
- Destructuring arrays and objects
- Managing state in applications
Exercises
- Create a function that takes any number of arguments and returns their sum.
- Write a function that merges two objects, but if there are conflicts, values from the second object should be arrays containing both values.
- Create a function that clones an array of objects deeply (not a shallow copy).
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)