Остаточные аргументы и оператор расширения

Далее, несколько слов об остаточных параметрах функций. В JavaScript можно задавать функции, принимающие неограниченное число аргументов. Например, стандартные функции Math.max и Math.min могут работать с произвольным числом входных параметров. Как объявлять такие функции? Это делается с помощью такого синтаксиса:

function sumAll(...args) {
    let sum = 0;
    for(let val of args)
         sum += val;
    return sum;
}

Здесь мы тремя точками говорим буквально следующее: нужно взять все переданные аргументы и поместить их в массив args. Далее уже идет работа с самим массивом.

console.log( sumAll(1, 2) );
console.log( sumAll(1, 2, 3, 4) );

Кстати, вспоминая работу с массивами, мы можем записать эту функцию гораздо короче:

function sumAll(...args) {
    return args.reduce((prevVal, value) => prevVal += value, 0);
}

Вот она сила и мощь языка JavaScript! Но почему массив этих аргументов называется остаточными параметрами? Дело в том, что мы можем комбинировать обычные аргументы и такой массив, например:

function sumAll(arg1, arg2, ...args) {
    return arg1 + arg2 + args.reduce((prevVal, value) => prevVal += value, 0);
}

и остаточные аргументы всегда должны быть записаны последними – в массив args собирается все, что мы передаем помимо первых двух аргументов. Например, при таком вызове:

console.log( sumAll(1, 2) );

массив args будет пустым. А при таком:

console.log( sumAll(1, 2, 3, 4) );

содержать значения 3 и 4 – остаточные параметры. Вот так это работает.

В противоположность остаточным аргументам, когда множество переданных значений помещаются в массив, в JavaScript существует оператор расширения, который разворачивает массив в набор отдельных значений. Как он записывается и зачем он нужен? Давайте опять обратимся к стандартной функции Math.max. Она находит максимальное значение из переданных значений. Но, что если наши значения хранятся в массиве?

let items = [1, 2, 3, 4, 5];

При вызове

let max = Math.max(items);
console.log( max );

получим значение NaN. Конечно, мы могли бы передать каждый элемент массива вот так:

let max = Math.max(items[0], items[1], ..., items[4]);

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

let max = Math.max(...items);

И теперь мы видим верное значение 5. Мы даже можем записать так сразу два массива:

let items = [1, 2, 3, 4, 5];
let digs = [-1, 0, 6, 10, 101];
let max = Math.max(...items, ...digs);

и найти максимум среди всех этих элементов. Или, комбинировать их с обычными значениями:

let max = Math.max(...items, 1000, ...digs, 0);

Кстати, используя этот механизм, можно делать объединение массивов:

let comp = [...items, ...digs];
console.log( comp );

А также вписывать туда отдельные значения:

let comp = [...items, -1, -2, -3, ...digs];

Как видите – это очень гибкий механизм. И он работает не только с массивами, но с любыми итерируемыми (перебираемыми) данными, например, со строками:

let letters = [..."Привет"];
console.log( letters );

Вам может показаться, что один и тот же оператор … используется как для остаточных параметров, так и для расширения. Но, в действительности, здесь есть четкое разделение:

  • если оператор … записан при объявлении функции в списке его аргументов, то это для сбора остаточных аргументов;
  • во всех остальных случаях … это оператор расширения итерируемых объектов.

Видео по теме