# Все о Spread и Rest


Введение

Многие встроенные функции JavaScript поддерживают произвольное количество аргументов.

Например:

  • Math.max(arg1, arg2, ..., argN) – вычисляет максимальное число из переданных.
  • Object.assign(dest, src1, ..., srcN) – копирует свойства из исходных объектов src1..N в целевой объект dest.
  • …и так далее.

В этой главе мы узнаем, как сделать то же самое с нашими собственными функциями и как передавать таким функциям параметры в виде массива.

Метод Spread

Подробнее: Синтаксис Spread

#Spread syntax позволяет расширить доступные для итерации элементы (например, массивы или строки) в местах

  • для функций: где ожидаемое количество аргументов для вызовов функций равно нулю или больше нуля;
  • для элементов (литералов массива);
  • для выражений объектов: в местах, где количество пар “ключ-значение” должно быть равно нулю или больше (для объектных литералов);

Практическое применение (с массивом)

  1. [… a] создаёт копию массива
  2. […a, …b] обьединяет массивы
  3. […a, ‘x’, …b] обьединяет массивы

Практическое применение (с обьектом)

НЕ ПРИМЕНЯЕТСЯ ТАК! . …{какая-то переменная} // TypeError (найден невызываемый итераторт)

ПРИМЕНЯЕТСЯ:

  1. ({…obj1})
  2. ({…obj1, …obj2}) // Копирует обьект из двух При обьединении обьектов, где совпадают наименования ключей, но их значения различны, за итоговый принимается тот, который последний при перечислении).

Практика

  1. Хотим найти максимальное число с массива Math.max(values: 5, 37, 42, 17) // 42
  2. Есть массив […] Math.max(numbers) // Nan !!! неверная формулировка Применим Spread Math.max(…numbers) // 42

Метод Rest

Подробнее: Синтаксис Rest

Синтаксис параметра#rest позволяет функции принимать неопределенное количество аргументов в виде массива, обеспечивая способ представления переменных функций в JavaScript.

Основное отличие rest от spread в области применения. Например:

function sum (a, b) {
return a + b
}
 
const numbers = [1, 2, 3, 4, 5]
console.log(sum(...numbers)) // 3 // Был использован Spread 
 
Была произведена сумма чисел 1 и 2. Числа 3, 4, 5 были проигнорированы, потому что нехватило операторов для их вычислений.
 
Используем Rest
 
function sum (a, b, ...rest)
console.log(rest) // Были собраны оставшиеся параметры в массив
 
1. Просуммируем все элементы массива
 
function sum (a, b, ...rest) {
return a + b + rest.reduce((a, i) => a + i, 0) // 15
}
 
2. Получим значение из массива
 
const a = numbers[0];
const b = numbers[1]
console.log(a, b)
 
Проведём деструктуризацию [[3 Деструктуризация]]
const [a, b] = numbers;
const [a, b, ...others] = numbers;
console.log(a, b, others) // Выведется 3 массива(обьекта) с числами.

Остаточные параметры (...)

Вызывать функцию можно с любым количеством аргументов независимо от того, как она была определена.

Например:

function sum(a, b) { return a + b; } alert( sum(1, 2, 3, 4, 5) );

Лишние аргументы не вызовут ошибку. Но, конечно, посчитаются только первые два.

Остаточные параметры могут быть обозначены через три точки .... Буквально это значит: «собери оставшиеся параметры и положи их в массив».

Например, соберём все аргументы в массив args:

function sumAll(...args) { // args — имя массива   
	let sum = 0;    
	for (let arg of args) sum += arg;    
	return sum; 
}  
 
alert( sumAll(1) ); // 1 
alert( sumAll(1, 2) ); // 3 
alert( sumAll(1, 2, 3) ); // 6`

Мы можем положить первые несколько параметров в переменные, а остальные – собрать в массив.

В примере ниже первые два аргумента функции станут именем и фамилией, а третий и последующие превратятся в массив titles:

function showName(firstName, lastName, ...titles) {   
	alert( firstName + ' ' + lastName ); // Юлий Цезарь    
	// Оставшиеся параметры пойдут в массив   // 
	
	titles = ["Консул", "Император"]   
	alert( titles[0] ); // Консул   
	alert( titles[1] ); // Император   
	
	alert( titles.length ); // 2 
}  
 
showName("Юлий", "Цезарь", "Консул", "Император");`

Остаточные параметры должны располагаться в конце

Остаточные параметры собирают все остальные аргументы, поэтому бессмысленно писать что-либо после них. Это вызовет ошибку:

function f(arg1, ...rest, arg2) { // arg2 после ...rest ?! // Ошибка }

...rest должен всегда быть последним.

Переменная “arguments”

Все аргументы функции находятся в псевдомассиве arguments под своими порядковыми номерами.

Например:

function showName() {   
	alert( arguments.length );   
	alert( arguments[0] );   
	alert( arguments[1] );    // Объект arguments можно перебирать   // 
	
	for (let arg of arguments) 
	alert(arg); 
}  
 
// Вывод: 2, Юлий, Цезарь showName("Юлий", "Цезарь");  
// Вывод: 1, Илья, undefined (второго аргумента нет) showName("Илья");`

Раньше в языке не было остаточных параметров, и получить все аргументы функции можно было только с помощью arguments. Этот способ всё ещё работает, мы можем найти его в старом коде.

Но у него есть один недостаток. Хотя arguments похож на массив, и его тоже можно перебирать, это всё же не массив. Он не поддерживает методы массивов, поэтому мы не можем, например, вызвать arguments.map(...).

К тому же, arguments всегда содержит все аргументы функции — мы не можем получить их часть. А остаточные параметры позволяют это сделать.

Соответственно, для более удобной работы с аргументами лучше использовать остаточные параметры.

Стрелочные функции не имеют "arguments"

Если мы обратимся к arguments из стрелочной функции, то получим аргументы внешней «нормальной» функции.

Пример:

function f() { let showArg = () => alert(arguments[0]); showArg(2); } f(1); // 1

Как мы помним, у стрелочных функций нет собственного this. Теперь мы знаем, что нет и своего объекта arguments.

Оператор расширения

Мы узнали, как получить массив из списка параметров. Но иногда нужно сделать в точности противоположное.

Например, есть встроенная функция Math.max. Она возвращает наибольшее число из списка:

alert( Math.max(3, 5, 1) ); // 5

Допустим, у нас есть массив чисел [3, 5, 1]. Как вызвать для него Math.max?

Просто так их не вставишь — Math.max ожидает получить список чисел, а не один массив.

let arr = [3, 5, 1]; _alert( Math.max(arr) ); // NaN_

Конечно, мы можем вводить числа вручную : Math.max(arr[0], arr[1], arr[2]). Но, во-первых, это плохо выглядит, а, во-вторых, мы не всегда знаем, сколько будет аргументов. Их может быть как очень много, так и не быть совсем.

И тут нам поможет оператор расширения. Он похож на остаточные параметры – тоже использует ..., но делает совершенно противоположное.

Когда ...arr используется при вызове функции, он «расширяет» перебираемый объект arr в список аргументов.

Для Math.max:

let arr = [3, 5, 1];  
alert( Math.max(...arr) ); // 5 (оператор "раскрывает" массив в список аргументов)`

Этим же способом мы можем передать несколько итерируемых объектов:

let arr1 = [1, -2, 3, 4]; 
let arr2 = [8, 3, -8, 1];  
alert( Math.max(...arr1, ...arr2) ); // 8`

Мы даже можем комбинировать оператор расширения с обычными значениями:

let arr1 = [1, -2, 3, 4]; 
let arr2 = [8, 3, -8, 1];  
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25`

Оператор расширения можно использовать и для слияния массивов:

let arr = [3, 5, 1]; 
let arr2 = [8, 9, 15];  
let merged = [0, ...arr, 2, ...arr2];_  a
lert(merged); // 0,3,5,1,2,8,9,15 (0, затем arr, затем 2, в конце arr2)`

В примерах выше мы использовали массив, чтобы продемонстрировать свойства оператора расширения, но он работает с любым перебираемым объектом.

Например, оператор расширения подойдёт для того, чтобы превратить строку в массив символов: let str = "Привет"; alert( [...str] ); // П,р,и,в,е,т

Посмотрим, что происходит. Под капотом оператор расширения использует итераторы, чтобы перебирать элементы. Так же, как это делает for..of.

Цикл for..of перебирает строку как последовательность символов, поэтому из ...str получается "П", "р", "и", "в", "е", "т". Получившиеся символы собираются в массив при помощи стандартного объявления массива: [...str].

Для этой задачи мы можем использовать и Array.from. Он тоже преобразует перебираемый объект (такой как строка) в массив:

let str = "Привет";  // Array.from преобразует перебираемый объект в массив 
alert( Array.from(str) ); // П,р,и,в,е,т`

Результат аналогичен [...str]. Но между Array.from(obj) и [...obj] есть разница:

  • Array.from работает как с псевдомассивами, так и с итерируемыми объектами
  • Оператор расширения работает только с итерируемыми объектами

Выходит, что если нужно сделать из чего угодно массив, то Array.from — более универсальный метод.

Итого

Когда мы видим "..." в коде, это могут быть как остаточные параметры, так и оператор расширения.

Как отличить их друг от друга:

  • Если ... располагается в конце списка параметров функции, то это «остаточные параметры». Он собирает остальные неуказанные аргументы и делает из них массив.
  • Если ... встретился в вызове функции или где-либо ещё, то это «оператор расширения». Он извлекает элементы из массива.

Полезно запомнить:

  • Остаточные параметры используются, чтобы создавать новые функции с неопределённым числом аргументов.
  • С помощью оператора расширения можно вставить массив в функцию, которая по умолчанию работает с обычным списком аргументов.

Вместе эти конструкции помогают легко преобразовывать наборы значений в массивы и обратно.

К аргументам функции можно обращаться и по-старому — через псевдомассив arguments.