Learn JavaScript

Spread и rest: копирование и сбор параметров

Оператор ... в JavaScript используется в двух разных смыслах:

  • spread - “развернуть” значения (копирование, объединение)
  • rest - “собрать остаток” (деструктуризация, параметры функций)

Синтаксис один и тот же (...), но смысл зависит от места, где он применяется.

1) Spread: массивы (копия и объединение)

Копия массива

const a = [1, 2, 3];
const b = [...a];

b[0] = 99;

console.log(a); // [1, 2, 3]
console.log(b); // [99, 2, 3]

Объединение массивов

const a = [1, 2];
const b = [3, 4];

const merged = [...a, ...b];
console.log(merged); // [1, 2, 3, 4]

Вставка элементов в середину

const a = [1, 2, 5, 6];

const withMiddle = [ ...a.slice(0, 2), 3, 4, ...a.slice(2) ];
console.log(withMiddle); // [1, 2, 3, 4, 5, 6]

2) Spread: объекты (копия и объединение)

Копия объекта

const a = { x: 1, y: 2 };
const b = { ...a };

b.x = 99;

console.log(a); // { x: 1, y: 2 }
console.log(b); // { x: 99, y: 2 }

Объединение объектов

Если ключи совпадают, выигрывает тот, что справа.

const base = { theme: "light", lang: "en" };
const overrides = { theme: "dark" };

const settings = { ...base, ...overrides };
console.log(settings); // { theme: "dark", lang: "en" }

Добавление/замена свойства при копировании

const user = { id: 1, name: "Sam" };

const updated = { ...user, name: "Alex" };
console.log(updated); // { id: 1, name: "Alex" }

3) Spread в вызове функции

Spread “разворачивает” массив в список аргументов.

function sum(a, b, c) {
  return a + b + c;
}

const nums = [1, 2, 3];

console.log(sum(...nums)); // 6

Это альтернатива ручной передаче nums[0], nums[1], nums[2].

4) Rest: массивы в деструктуризации

Rest собирает оставшиеся элементы в массив.

const nums = [10, 20, 30, 40];

const [first, second, ...rest] = nums;

console.log(first);  // 10
console.log(second); // 20
console.log(rest);   // [30, 40]

5) Rest: объекты в деструктуризации

Rest собирает оставшиеся свойства в новый объект.

const user = { id: 1, name: "Sam", age: 20 };

const { id, ...rest } = user;

console.log(id);   // 1
console.log(rest); // { name: "Sam", age: 20 }

6) Rest parameters: сбор аргументов функции

Rest в параметрах функции собирает “хвост” аргументов в массив.

function sumAll(...nums) {
  return nums.reduce((acc, n) => acc + n, 0);
}

console.log(sumAll(1, 2, 3));    // 6
console.log(sumAll(10, 20, 30)); // 60

Можно комбинировать обычные параметры и rest:

function log(level, ...messages) {
  console.log(level, messages);
}

log("info", "a", "b", "c"); // "info" ["a", "b", "c"]

Rest должен быть последним параметром.

7) Поверхностная копия: важный нюанс

Spread делает поверхностную копию. Если внутри лежат объекты, копируются ссылки.

const a = { user: { name: "Sam" } };
const b = { ...a };

b.user.name = "Alex";

console.log(a.user.name); // "Alex"
console.log(b.user.name); // "Alex"

Копия сделана только на верхнем уровне.

Частые ошибки

1) Путаница spread и rest

Spread “разворачивает”, rest “собирает”.

const nums = [1, 2, 3];

function f(a, b, c) {
  return a + b + c;
}

console.log(f(...nums)); // spread
function g(...args) {
  return args.length;
}

console.log(g(1, 2, 3)); // rest

2) Rest не может быть не последним параметром

try {
  eval("function bad(...args, last) { return last; }");
} catch (e) {
  console.log(e.name); // "SyntaxError"
}

Что стоит запомнить

  • ... это либо spread, либо rest - зависит от контекста.
  • Spread: копировать и объединять массивы/объекты, передавать массив как аргументы.
  • Rest: собирать остаток в деструктуризации и собирать аргументы функции.
  • Spread делает поверхностную копию, вложенные объекты остаются общими по ссылке.