В lodash функция _.merge(obj, ...sources) принимает любое количество объектов для слияния. В React clsx('btn', isActive && 'btn-active', className) принимает любое число классов. Синтаксис ... делает эти API возможными.
Оба используют ..., но делают противоположное:
...rest в деструктуризации — тот же синтаксисДолжен быть последним параметром. Заменяет устаревший arguments:
function sum(...numbers) {
// numbers — обычный массив со всеми аргументами
return numbers.reduce((acc, n) => acc + n, 0)
}
console.log(sum(1, 2, 3, 4, 5)) // 15
console.log(sum(10, 20)) // 30
console.log(sum()) // 0
// Фиксированные параметры + остаток:
function log(level, timestamp, ...messages) {
console.log(`[${level}] ${timestamp}:`, ...messages)
}
log('INFO', '10:30', 'Запрос', 'GET /api/users', '200 OK')
// [INFO] 10:30: Запрос GET /api/users 200 OKВ вызове функции:
const nums = [3, 1, 4, 1, 5, 9, 2, 6]
Math.max(...nums) // 9 — как Math.max(3, 1, 4, 1, 5, 9, 2, 6)
Math.min(...nums) // 1
// Строка тоже iterable:
console.log([...'hello']) // ['h', 'e', 'l', 'l', 'o']Копирование и слияние массивов:
const fruits = ['яблоко', 'банан']
const veggies = ['морковь', 'лук']
const all = [...fruits, ...veggies] // ['яблоко', 'банан', 'морковь', 'лук']
const copy = [...fruits] // поверхностная копия
const mixed = ['авокадо', ...fruits, 'дыня'] // вставка в серединуСлияние объектов (правые ключи перезаписывают левые):
const defaults = { theme: 'dark', lang: 'ru', fontSize: 14, notifications: true }
const userPrefs = { theme: 'light', notifications: false }
const config = { ...defaults, ...userPrefs }
// { theme: 'light', lang: 'ru', fontSize: 14, notifications: false }
// Обновление одного поля — иммутабельно:
const newConfig = { ...config, lang: 'en' }Spread не делает глубокую копию:
const user = { name: 'Иван', address: { city: 'Москва' } }
const copy = { ...user }
copy.address.city = 'СПб' // изменит и user.address.city!
// Для глубокой копии: structuredClone(user) или вложенный spreadОшибка 1: rest не последний параметр
// Сломано — SyntaxError:
function bad(a, ...rest, b) { }
// Исправлено — rest только последний:
function good(a, b, ...rest) { }Ошибка 2: spread не-итерируемого
// Сломано:
const obj = { a: 1 }
console.log([...obj]) // TypeError: obj is not iterable
// Spread объекта работает только в объектном контексте:
const copy = { ...obj } // OKОшибка 3: spread vs concat
// Оба делают одно, но spread нагляднее:
const merged1 = arr1.concat(arr2) // старый способ
const merged2 = [...arr1, ...arr2] // современный
// Но при большом количестве массивов concat эффективнее:
const mergedMany = [].concat(...manyArrays)<Component {...props} /> — передача всех пропсовreturn { ...state, loading: true } — иммутабельное обновление statefunction request(url, { method = 'GET', ...options } = {}){ ...defaultConfig, ...envConfig, ...userConfig }Утилиты для работы с данными: pick, omit, merge
// Утилита pick — выбрать только нужные ключи
function pick(obj, ...keys) {
return keys.reduce((result, key) => {
if (key in obj) result[key] = obj[key]
return result
}, {})
}
// Утилита omit — исключить ключи
function omit(obj, ...keys) {
const excluded = new Set(keys)
return Object.fromEntries(
Object.entries(obj).filter(([k]) => !excluded.has(k))
)
}
// Глубокое слияние (merge)
function mergeDeep(...objects) {
return objects.reduce((result, obj) => {
for (const [key, value] of Object.entries(obj)) {
if (value && typeof value === 'object' && !Array.isArray(value)) {
result[key] = mergeDeep(result[key] ?? {}, value)
} else {
result[key] = value
}
}
return result
}, {})
}
const user = {
id: 1,
name: 'Алексей',
email: 'alex@mail.ru',
password: 'hashed_secret',
role: 'admin',
settings: { theme: 'dark', notifications: true }
}
// Безопасный объект для API-ответа (без пароля)
const safeUser = omit(user, 'password')
console.log(safeUser)
// { id: 1, name: 'Алексей', email: 'alex@mail.ru', role: 'admin', settings: {...} }
// Только публичные поля
const publicUser = pick(user, 'id', 'name', 'role')
console.log(publicUser) // { id: 1, name: 'Алексей', role: 'admin' }
// Глубокое слияние настроек
const defaults = { theme: 'dark', editor: { tabSize: 2, wrap: false } }
const overrides = { editor: { tabSize: 4, spell: true } }
const merged = mergeDeep(defaults, overrides)
console.log(merged)
// { theme: 'dark', editor: { tabSize: 4, wrap: false, spell: true } }
// Вариативная функция логгера
function logger(level, ...messages) {
const timestamp = new Date().toISOString().slice(11, 19)
const text = messages.map(m =>
typeof m === 'object' ? JSON.stringify(m) : String(m)
).join(' ')
console.log(`[${timestamp}] [${level.toUpperCase()}] ${text}`)
}
logger('info', 'Пользователь', { id: 1 }, 'вошёл в систему')
// [10:30:45] [INFO] Пользователь {"id":1} вошёл в системуВ lodash функция _.merge(obj, ...sources) принимает любое количество объектов для слияния. В React clsx('btn', isActive && 'btn-active', className) принимает любое число классов. Синтаксис ... делает эти API возможными.
Оба используют ..., но делают противоположное:
...rest в деструктуризации — тот же синтаксисДолжен быть последним параметром. Заменяет устаревший arguments:
function sum(...numbers) {
// numbers — обычный массив со всеми аргументами
return numbers.reduce((acc, n) => acc + n, 0)
}
console.log(sum(1, 2, 3, 4, 5)) // 15
console.log(sum(10, 20)) // 30
console.log(sum()) // 0
// Фиксированные параметры + остаток:
function log(level, timestamp, ...messages) {
console.log(`[${level}] ${timestamp}:`, ...messages)
}
log('INFO', '10:30', 'Запрос', 'GET /api/users', '200 OK')
// [INFO] 10:30: Запрос GET /api/users 200 OKВ вызове функции:
const nums = [3, 1, 4, 1, 5, 9, 2, 6]
Math.max(...nums) // 9 — как Math.max(3, 1, 4, 1, 5, 9, 2, 6)
Math.min(...nums) // 1
// Строка тоже iterable:
console.log([...'hello']) // ['h', 'e', 'l', 'l', 'o']Копирование и слияние массивов:
const fruits = ['яблоко', 'банан']
const veggies = ['морковь', 'лук']
const all = [...fruits, ...veggies] // ['яблоко', 'банан', 'морковь', 'лук']
const copy = [...fruits] // поверхностная копия
const mixed = ['авокадо', ...fruits, 'дыня'] // вставка в серединуСлияние объектов (правые ключи перезаписывают левые):
const defaults = { theme: 'dark', lang: 'ru', fontSize: 14, notifications: true }
const userPrefs = { theme: 'light', notifications: false }
const config = { ...defaults, ...userPrefs }
// { theme: 'light', lang: 'ru', fontSize: 14, notifications: false }
// Обновление одного поля — иммутабельно:
const newConfig = { ...config, lang: 'en' }Spread не делает глубокую копию:
const user = { name: 'Иван', address: { city: 'Москва' } }
const copy = { ...user }
copy.address.city = 'СПб' // изменит и user.address.city!
// Для глубокой копии: structuredClone(user) или вложенный spreadОшибка 1: rest не последний параметр
// Сломано — SyntaxError:
function bad(a, ...rest, b) { }
// Исправлено — rest только последний:
function good(a, b, ...rest) { }Ошибка 2: spread не-итерируемого
// Сломано:
const obj = { a: 1 }
console.log([...obj]) // TypeError: obj is not iterable
// Spread объекта работает только в объектном контексте:
const copy = { ...obj } // OKОшибка 3: spread vs concat
// Оба делают одно, но spread нагляднее:
const merged1 = arr1.concat(arr2) // старый способ
const merged2 = [...arr1, ...arr2] // современный
// Но при большом количестве массивов concat эффективнее:
const mergedMany = [].concat(...manyArrays)<Component {...props} /> — передача всех пропсовreturn { ...state, loading: true } — иммутабельное обновление statefunction request(url, { method = 'GET', ...options } = {}){ ...defaultConfig, ...envConfig, ...userConfig }Утилиты для работы с данными: pick, omit, merge
// Утилита pick — выбрать только нужные ключи
function pick(obj, ...keys) {
return keys.reduce((result, key) => {
if (key in obj) result[key] = obj[key]
return result
}, {})
}
// Утилита omit — исключить ключи
function omit(obj, ...keys) {
const excluded = new Set(keys)
return Object.fromEntries(
Object.entries(obj).filter(([k]) => !excluded.has(k))
)
}
// Глубокое слияние (merge)
function mergeDeep(...objects) {
return objects.reduce((result, obj) => {
for (const [key, value] of Object.entries(obj)) {
if (value && typeof value === 'object' && !Array.isArray(value)) {
result[key] = mergeDeep(result[key] ?? {}, value)
} else {
result[key] = value
}
}
return result
}, {})
}
const user = {
id: 1,
name: 'Алексей',
email: 'alex@mail.ru',
password: 'hashed_secret',
role: 'admin',
settings: { theme: 'dark', notifications: true }
}
// Безопасный объект для API-ответа (без пароля)
const safeUser = omit(user, 'password')
console.log(safeUser)
// { id: 1, name: 'Алексей', email: 'alex@mail.ru', role: 'admin', settings: {...} }
// Только публичные поля
const publicUser = pick(user, 'id', 'name', 'role')
console.log(publicUser) // { id: 1, name: 'Алексей', role: 'admin' }
// Глубокое слияние настроек
const defaults = { theme: 'dark', editor: { tabSize: 2, wrap: false } }
const overrides = { editor: { tabSize: 4, spell: true } }
const merged = mergeDeep(defaults, overrides)
console.log(merged)
// { theme: 'dark', editor: { tabSize: 4, wrap: false, spell: true } }
// Вариативная функция логгера
function logger(level, ...messages) {
const timestamp = new Date().toISOString().slice(11, 19)
const text = messages.map(m =>
typeof m === 'object' ? JSON.stringify(m) : String(m)
).join(' ')
console.log(`[${timestamp}] [${level.toUpperCase()}] ${text}`)
}
logger('info', 'Пользователь', { id: 1 }, 'вошёл в систему')
// [10:30:45] [INFO] Пользователь {"id":1} вошёл в системуНапиши функцию pipeline(...fns), которая принимает любое количество функций-преобразователей и возвращает новую функцию. Эта новая функция принимает начальное значение и последовательно применяет к нему все переданные функции (результат каждой передаётся следующей). Это паттерн «конвейер» (pipe).
fns.reduce((value, fn) => fn(value), value) — начни с начального значения и применяй каждую функцию по очереди.