JavaScript Оператор Rest Упаковка аргументов

Оператор Rest Аргументы могут быть любого типа - числа, строки, массивы и другие. Работа с аргументами JavaScript. Реализуем простую функцию.

3 мин
Автор PINTA IT
JavaScript Оператор Rest Упаковка аргументов

JavaScript Оператор Rest Упаковка аргументов

Давайте попробуем реализовать очень простую функцию, суммирующую числа. Для начала определим функцию sum, принимающую на вход два числа и возвращающую их сумму:

const sum = (a, b) => a + b;

sum(1, 2);   // 3
sum(-3, 10); // 7

Пока всё просто и понятно. Сложности возникают при дополнительных требованиях: что, если захотим суммировать не два, а три числа? Или пять, или даже десять? Писать для обработки каждого случая отдельную функцию - очевидно плохой, непрактичный вариант:

const sumOfTwo = (a, b) => a + b;
const sumOfTree = (a, b, c) => a + b + c;
const sumOfTen = (a, b, c, d, e, f, g, h, i, j) => a + b + c + d + e + f + g + h + i + j; // фух...
// const sumoOfThousand = ???
// const sumOfMillion = ???

Надо, чтобы единая функция могла работать с разным количеством аргументов. Как это сделать?

Можно заметить, что в стандартной библиотеке JavaScript существуют функции, которые могут принимать разное количество аргументов. Например, сигнатура функции Math.max определяется так:

Math.max([value1[, value2[, ...]]])

Она говорит нам о том, что в Math.max можно передать любое количество элементов:

Math.max(10, 20);             // 20
Math.max(10, 20, 30);         // 30
Math.max(10, 20, 30, 40, 50); // 50
Math.max(-10, -20, -30);      // -10

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

const func = (...params) => {
  // params — это массив, содержащий все
  // переданные при вызове функции аргументы
  console.log(params);
};

func();            // => []
func(9);           // => [9]
func(9, 4);        // => [9, 4]
func(9, 4, 1);     // => [9, 4, 1]
func(9, 4, 1, -3); // => [9, 4, 1, -3]

Символ троеточия ... перед именем формального параметра в определении функции обозначает rest-оператор. Запись ...params в определении func из примера выше означает буквально следующее: "все переданные при вызове функции аргументы поместить в массив params".

Если вовсе не передать аргументов, то rest-массив params будет пустым:

func(); // => []

В функцию можно передать любое количество аргументов — все они попадут в rest-массив params:

func(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Аргументы могут быть любого типа - числа, строки, массивы и др.:

func(1, 2, 'hello', [3, 4, 5], true);
// => [1, 2, 'hello', [3, 4, 5 ], true]

Основная сложность для понимания оператора ... состоит в том, что он выполняет различные действия в зависимости от того, где применяется. В определении функции он выполняет "упаковку" параметров, а при вызове - наоборот, "распаковку". Упаковке параметров посвящён этот урок и вы уже видели, как это делается. В следующем же уроке рассмотрим ... в роли распаковщика (spread-оператор).

Теперь у нас достаточно знаний, чтобы с помощью rest-оператора переписать нашу фунцию sum так, чтобы она умела суммировать любое количество чисел (а не только два числа, как сейчас):

const sum = (...numbers) => {
  let result = 0;
  for (let num of numbers) {
    result += num;
  }
  return result;
};

sum();         // 0
sum(10);       // 10
sum(10, 4);    // 14
sum(8, 10, 4); // 22

Наша реализация sum имеет интересную особенность: мы решили сделать так, что при вызове без аргументов возвращается значение 0 ("Ничего"), при вызове с одним числом возвращается это же число. Хотя можно было обработать эти ситуации как-то по-другому (например, вернуть специальное значение null), это вопрос удобного выбора, подходящего для решения конкретных практических задач.

В таком контексте rest-массив можно считать, как "необязательные аргументы", которые можно либо вовсе не передавать либо передавать столько, сколько хочешь. А что, если мы захотим, чтобы функция имела два обыкновенных ("обязательных") именованных параметра, а остальные были необязательными и сохранялись в rest-массиве? Всё просто: при определении функции сначала указываем стандартные именованные формальные параметры (например, a и b) и в конце добавляем rest-массив:

const func = (a, b, ...params) => {
  // параметр 'a' содержит первый аргумент
  console.log(`a -> ${a}`);
  // параметр 'b' содержит второй аргумент
  console.log(`b -> ${b}`);
  // params содержит все остальные аргументы
  console.log(params);
};

func(9, 4);
// => a -> 9
// => b -> 4
// => []
func(9, 4, 1);
// => a -> 9
// => b -> 4
// => [1]
func(9, 4, 1, -3);
// => a -> 9
// => b -> 4
// => [1, -3]
func(9, 4, 1, -3, -5);
// => a -> 9
// => b -> 4
// => [1, -3, -5]

То же можно сделать и для одного аргумента:

const func = (a, ...params) => {
  // ...
};

и для трёх:

const func = (a, b, c, ...params) => {
  // ...
};

Эту идею можно продолжать и дальше, делая обязательными то количество аргументов, которое требуется. Единственное ограничение: rest-оператор может быть использован только для последнего параметра. То есть такой код синтаксически неверен:

const func = (...params, a) => {
  // ...
};

И такой тоже:

const func = (a, ...params, b) => {
  // ...
};

Именно поэтому оператор называется rest, то есть он организует хранение "остальных" ("оставшихся", последних) параметров.

JavaScript 

Лучшие бесплатные онлайн курсы по JavaScript - Научись писать код на JavaScript и создавать потрясающие веб приложения. Большая коллекция курсов JavaScript.

Учить JavaScript

Похожие публикации