Операторы spread и rest — это синтаксис ES6, использующий три точки (...
). Spread “разворачивает” элементы массива или свойства объекта, а rest “собирает” множественные элементы в массив или объект. Spread используется при вызове функций и создании новых структур данных, rest — в параметрах функций и деструктуризации.
Ключевые различия:
[...array]
, {...object}
)function(...args)
, [first, ...rest]
)Операторы spread и rest — это мощные инструменты ES6, которые используют одинаковый синтаксис (...
), но выполняют противоположные функции. Они упрощают работу с массивами, объектами и функциями, делая код более читаемым и функциональным.
// Spread — разворачивает элементы
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5]; // [1, 2, 3, 4, 5]
// Rest — собирает элементы
function sum(...args) {
return args.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
const original = [1, 2, 3];
const copy = [...original];
console.log(copy); // [1, 2, 3]
console.log(original === copy); // false — разные объекты
// Традиционный способ
const oldCopy = original.slice();
// или
const oldCopy2 = Array.from(original);
const fruits = ['яблоко', 'банан'];
const vegetables = ['морковь', 'капуста'];
const food = [...fruits, ...vegetables];
console.log(food); // ['яблоко', 'банан', 'морковь', 'капуста']
// Добавление элементов
const numbers = [2, 3, 4];
const extended = [1, ...numbers, 5, 6];
console.log(extended); // [1, 2, 3, 4, 5, 6]
// Вставка в середину
const start = [1, 2];
const middle = [3, 4];
const end = [5, 6];
const combined = [...start, ...middle, ...end];
console.log(combined); // [1, 2, 3, 4, 5, 6]
const word = 'привет';
const letters = [...word];
console.log(letters); // ['п', 'р', 'и', 'в', 'е', 'т']
// Работа с эмодзи
const emoji = '👨👩👧👦';
const emojiArray = [...emoji];
console.log(emojiArray); // Корректно разбивает составные эмодзи
const user = {
name: 'Иван',
age: 25,
city: 'Москва'
};
const userCopy = {...user};
console.log(userCopy); // {name: 'Иван', age: 25, city: 'Москва'}
console.log(user === userCopy); // false
// Поверхностное копирование
const userWithAddress = {
name: 'Мария',
address: {street: 'Тверская', house: 10}
};
const copy = {...userWithAddress};
copy.address.house = 20; // Изменяет исходный объект!
console.log(userWithAddress.address.house); // 20
const basicInfo = {name: 'Петр', age: 30};
const contactInfo = {email: 'petr@example.com', phone: '+7-123-456-78-90'};
const fullInfo = {...basicInfo, ...contactInfo};
console.log(fullInfo);
// {name: 'Петр', age: 30, email: 'petr@example.com', phone: '+7-123-456-78-90'}
// Перезапись свойств
const defaults = {theme: 'light', language: 'ru', notifications: true};
const userSettings = {theme: 'dark', notifications: false};
const settings = {...defaults, ...userSettings};
console.log(settings);
// {theme: 'dark', language: 'ru', notifications: false}
const user = {name: 'Анна', age: 28};
const userWithId = {id: 1, ...user};
const userWithStatus = {...user, isActive: true, lastLogin: new Date()};
console.log(userWithId); // {id: 1, name: 'Анна', age: 28}
console.log(userWithStatus); // {name: 'Анна', age: 28, isActive: true, lastLogin: ...}
// Передача элементов массива как отдельных аргументов
function multiply(a, b, c) {
return a * b * c;
}
const numbers = [2, 3, 4];
console.log(multiply(...numbers)); // 24
// Поиск максимального значения
const scores = [85, 92, 78, 96, 88];
const maxScore = Math.max(...scores);
console.log(maxScore); // 96
// Добавление элементов в массив
const existingItems = ['item1', 'item2'];
const newItems = ['item3', 'item4'];
existingItems.push(...newItems);
console.log(existingItems); // ['item1', 'item2', 'item3', 'item4']
// Сумма любого количества чисел
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
console.log(sum()); // 0
// Функция с обязательными и опциональными параметрами
function greet(greeting, ...names) {
return `${greeting}, ${names.join(' и ')}!`;
}
console.log(greet('Привет', 'Иван')); // 'Привет, Иван!'
console.log(greet('Добро пожаловать', 'Мария', 'Петр')); // 'Добро пожаловать, Мария и Петр!'
// Логирование с разными уровнями
function log(level, message, ...details) {
console.log(`[${level.toUpperCase()}] ${message}`);
if (details.length > 0) {
console.log('Детали:', details);
}
}
log('info', 'Пользователь вошел в систему');
log('error', 'Ошибка подключения', 'timeout', 'server unreachable');
// Создание обертки для функций
function withLogging(fn) {
return function(...args) {
console.log('Вызов функции с аргументами:', args);
const result = fn(...args);
console.log('Результат:', result);
return result;
};
}
const loggedSum = withLogging(sum);
console.log(loggedSum(1, 2, 3)); // Логирует вызов и результат
// Извлечение первого элемента и ;остальных
const numbers = [1, 2, 3, 4, 5];
const [first, ...rest] = numbers;
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
// Извлечение нескольких элементов
const colors = ['красный', 'зеленый', 'синий', 'желтый', 'фиолетовый'];
const [primary, secondary, ...others] = colors;
console.log(primary); // 'красный'
console.log(secondary); // 'зеленый'
console.log(others); // ['синий', 'желтый', 'фиолетовый']
// Получение последних элементов
function getLastElements(arr, count) {
const [...allElements] = arr;
return allElements.slice(-count);
}
console.log(getLastElements([1, 2, 3, 4, 5], 2)); // [4, 5]
// Извлечение определенных свойств
const user = {
id: 1,
name: 'Дмитрий',
email: 'dmitry@example.com',
age: 35,
city: 'Санкт-Петербург',
phone: '+7-987-654-32-10'
};
const {id, name, ...otherInfo} = user;
console.log(id); // 1
console.log(name); // 'Дмитрий'
console.log(otherInfo); // {email: '...', age: 35, city: '...', phone: '...'}
// Исключение чувствительных данных
const userWithPassword = {
id: 2,
username: 'maria_user',
password: 'secret123',
email: 'maria@example.com',
role: 'admin'
};
const {password, ...safeUserData} = userWithPassword;
console.log(safeUserData); // Объект без пароля
// Обновление состояния с сохранением предыдущих значений
function updateUserProfile(currentUser, updates) {
return {
...currentUser,
...updates,
updatedAt: new Date()
};
}
const user = {id: 1, name: 'Иван', email: 'ivan@example.com'};
const updatedUser = updateUserProfile(user, {name: 'Иван Петров'});
console.log(updatedUser);
// {id: 1, name: 'Иван Петров', email: 'ivan@example.com', updatedAt: ...}
// Добавление элемента в массив состояния
function addTodo(todos, newTodo) {
return [...todos, {id: Date.now(), ...newTodo}];
}
const todos = [{id: 1, text: 'Купить молоко', done: false}];
const newTodos = addTodo(todos, {text: 'Выгулять собаку', done: false});
console.log(newTodos); // Новый массив с добавленной задачей
// Обработка ответа API
function processApiResponse(response) {
const {data, meta, ...otherFields} = response;
return {
items: data.items || [],
pagination: meta.pagination || {},
additionalInfo: otherFields
};
}
// Подготовка данных для отправки
function prepareUserData(formData, additionalFields = {}) {
const {confirmPassword, ...userData} = formData;
return {
...userData,
...additionalFields,
createdAt: new Date().toISOString()
};
}
// Функция для объединения массивов без дубликатов
function uniqueConcat(...arrays) {
const combined = [].concat(...arrays);
return [...new Set(combined)];
}
const arr1 = [1, 2, 3];
const arr2 = [3, 4, 5];
const arr3 = [5, 6, 7];
console.log(uniqueConcat(arr1, arr2, arr3)); // [1, 2, 3, 4, 5, 6, 7]
// Функция для глубокого объединения объектов
function deepMerge(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return deepMerge(target, ...sources);
}
// Композиция функций
function compose(...functions) {
return function(value) {
return functions.reduceRight((acc, fn) => fn(acc), value);
};
}
const addOne = x => x + 1;
const double = x => x * 2;
const square = x => x * x;
const composedFunction = compose(square, double, addOne);
console.log(composedFunction(3)); // ((3 + 1) * 2)² = 64
// Каррирование с rest параметрами
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return function(...nextArgs) {
return curried(...args, ...nextArgs);
};
};
}
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
Аспект | Spread | Rest |
---|---|---|
Синтаксис | ...expression | ...parameter |
Позиция | Правая часть выражения | Левая часть, параметры |
Функция | Разворачивает элементы | Собирает элементы |
Массивы | [...arr1, ...arr2] | [first, ...rest] |
Объекты | {...obj1, ...obj2} | {prop, ...others} |
Функции | fn(...args) | function(...params) |
Результат | Новая структура данных | Массив или объект |
Применение | Копирование, объединение | Сбор параметров |
// Условное добавление свойств
function createUser(name, email, isAdmin = false) {
return {
name,
email,
createdAt: new Date(),
...(isAdmin && {role: 'admin', permissions: ['read', 'write', 'delete']})
};
}
console.log(createUser('Иван', 'ivan@example.com')); // Без роли
console.log(createUser('Мария', 'maria@example.com', true)); // С ролью admin
// Условное объединение массивов
function getMenuItems(isAuthenticated, isAdmin) {
const baseItems = ['Главная', 'О нас', 'Контакты'];
const userItems = ['Профиль', 'Настройки'];
const adminItems = ['Панель управления', 'Пользователи'];
return [
...baseItems,
...(isAuthenticated ? userItems : []),
...(isAdmin ? adminItems : [])
];
}
// Динамическое создание объектов
function createDynamicObject(key, value, additionalProps = {}) {
return {
id: Math.random(),
timestamp: Date.now(),
[key]: value,
...additionalProps
};
}
const obj1 = createDynamicObject('username', 'john_doe', {email: 'john@example.com'});
const obj2 = createDynamicObject('productName', 'Laptop', {price: 999, category: 'Electronics'});
// Иммутабельное обновление вложенных объектов
function updateNestedProperty(obj, path, value) {
const [head, ...tail] = path;
if (tail.length === 0) {
return {...obj, [head]: value};
}
return {
...obj,
[head]: updateNestedProperty(obj[head] || {}, tail, value)
};
}
const state = {
user: {
profile: {
settings: {
theme: 'light'
}
}
}
};
const newState = updateNestedProperty(state, ['user', 'profile', 'settings', 'theme'], 'dark');
console.log(newState.user.profile.settings.theme); // 'dark'
console.log(state.user.profile.settings.theme); // 'light' — исходный не изменился
// ❌ Избегайте spread в циклах для больших массивов
let result = [];
for (let i = 0; i < 10000; i++) {
result = [...result, i]; // Создает новый массив каждый раз
}
// ✅ Используйте push для накопления
let result = [];
for (let i = 0; i < 10000; i++) {
result.push(i);
}
// ✅ Или используйте spread для финального объединения
const chunks = [];
for (let i = 0; i < 100; i++) {
chunks.push(Array.from({length: 100}, (_, j) => i * 100 + j));
}
const result = [].concat(...chunks);
// ❌ Слишком много spread операций
const result = {...obj1, ...obj2, ...obj3, ...obj4, ...obj5};
// ✅ Группируйте логически связанные операции
const baseConfig = {...defaultConfig, ...userConfig};
const finalConfig = {...baseConfig, ...environmentConfig};
// ❌ Неясное использование rest
function processData({a, b, c, d, e, f, ...rest}) {
// Слишком много параметров
}
// ✅ Группируйте связанные параметры
function processData({userInfo, settings, ...metadata}) {
const {name, email} = userInfo;
const {theme, language} = settings;
// Более понятная структура
}
// ❌ Небезопасное использование rest
function unsafeFunction(...args) {
return args[0].toUpperCase(); // Ошибка если args[0] не строка
}
// ✅ Валидация параметров
function safeFunction(...args) {
if (args.length === 0) {
throw new Error('Требуется хотя бы один аргумент');
}
const [first] = args;
if (typeof first !== 'string') {
throw new Error('Первый аргумент должен быть строкой');
}
return first.toUpperCase();
}
// ✅ Использование значений по умолчанию
function createUser({name, email, ...options} = {}) {
if (!name || !email) {
throw new Error('Имя и email обязательны');
}
return {
name,
email,
isActive: true,
createdAt: new Date(),
...options
};
}
// Безопасное разворачивание потенциально undefined значений
const user = {
name: 'Иван',
preferences: null
};
const settings = {
theme: 'light',
...user.preferences, // Ошибка!
};
// ✅ Безопасная версия
const safeSettings = {
theme: 'light',
...(user.preferences ?? {})
};
// ✅ С опциональной цепочкой
const config = {
...defaultConfig,
...user?.preferences,
...user?.settings?.ui
};
// Создание публичного API из объекта с приватными данными
function createPublicUser(userData) {
const {password, internalId, ...publicData} = userData;
return {
...publicData,
id: internalId,
hasPassword: Boolean(password)
};
}
// Типизированный spread
interface User {
id: number;
name: string;
email: string;
}
interface UserUpdate {
name?: string;
email?: string;
}
function updateUser(user: User, updates: UserUpdate): User {
return {
...user,
...updates
};
}
// Rest с типизацией
function logMessages(level: string, ...messages: string[]): void {
console.log(`[${level}]`, ...messages);
}
Условие: Создайте функцию, которая принимает любое количество массивов и возвращает новый массив со всеми уникальными элементами.
function mergeUnique(...arrays) {
// Ваш код здесь
}
console.log(mergeUnique([1, 2], [2, 3], [3, 4])); // [1, 2, 3, 4]
console.log(mergeUnique(['a', 'b'], ['b', 'c'], ['c', 'd'])); // ['a', 'b', 'c', 'd']
function mergeUnique(...arrays) {
const combined = [].concat(...arrays);
return [...new Set(combined)];
}
// Альтернативное решение
function mergeUnique(...arrays) {
return [...new Set(arrays.flat())];
}
Условие: Создайте функцию для обновления пользователя, которая принимает исходный объект пользователя и объект с обновлениями, возвращая новый объект с обновленными данными и временной меткой.
function updateUserWithTimestamp(user, updates) {
// Ваш код здесь
// Должна добавлять поле updatedAt с текущей датой
}
const user = {id: 1, name: 'Иван', email: 'ivan@example.com'};
const updates = {name: 'Иван Петров', city: 'Москва'};
const result = updateUserWithTimestamp(user, updates);
console.log(result);
// {id: 1, name: 'Иван Петров', email: 'ivan@example.com', city: 'Москва', updatedAt: ...}
function updateUserWithTimestamp(user, updates) {
return {
...user,
...updates,
updatedAt: new Date()
};
}
Условие: Создайте функцию для создания HTML элемента, которая принимает тег, содержимое и любое количество атрибутов.
function createElement(tag, content, ...attributes) {
// Ваш код здесь
// attributes — массив объектов вида {name: 'class', value: 'button'}
}
console.log(createElement('div', 'Привет мир'));
// '<div>Привет мир</div>'
console.log(createElement('button', 'Нажми меня',
{name: 'class', value: 'btn'},
{name: 'id', value: 'submit-btn'}
));
// '<button class="btn" id="submit-btn">Нажми меня</button>'
function createElement(tag, content, ...attributes) {
const attrs = attributes
.map(attr => `${attr.name}="${attr.value}"`)
.join(' ');
const attrString = attrs ? ` ${attrs}` : '';
return `<${tag}${attrString}>${content}</${tag}>`;
}
Операторы spread и rest — это мощные инструменты современного JavaScript, которые значительно упрощают работу с массивами, объектами и функциями. Они способствуют написанию более чистого, читаемого и функционального кода.
Ключевые преимущества:
Понимание различий между spread и rest, а также знание лучших практик их использования, поможет вам писать более эффективный и поддерживаемый код.
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и совершенствуйтесь каждый день 💪