Зарегистрируйтесь, чтобы продолжить обучение

Операция spread JS: Коллекции

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

Базовое использование spread

spread переводится как растягивание. Она берет коллекцию и растягивает ее на отдельные элементы. Посмотрим ее базовое использование.

Мы можем задать список таким образом:

const numbers = l(1, 10, 23, 234)

Здесь мы перечисляем все аргументы через запятую и на выходе получаем некоторую коллекцию. А можем сделать и по-другому:

const numbers = l(...[1, 10, 23, 234])

Здесь мы передаем массив, но слева поставили три точки. Вызов будет преобразован в l( , 10, 23, 234).

В этом случае мы видим похожий подход, но это не rest, а именно spread. Используются так же три точки, но в зависимости от места применения это либо rest, либо spread. В данном случае операция применяется к коллекциям, поэтому это spread.

Общая форма выглядит так:

myFunction(...iterableObj)

Здесь мы вызываем функцию и передаем туда некий iterable object, например, массив.

В итоге ставим три точки в начале, и это означает, что это spread, который будет растягивать коллекцию на элементы и вызывать функцию соответствующим количеством аргументов.

При этом spread в отличие от rest используется шире и не только в вызове функций.

Аргументы

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

Поскольку rest используется только в конце, с ним всё гораздо проще. А spread может использоваться в любом месте. Рассмотрим на примере.

Допустим, у нас есть функция, которая ожидает на вход пять параметров:

const fn = (v, w, x, y, z) => {};
const args = [0, 1];
fn(-1, ..args, 2, ...[3]);

Мы передаем ей первый параметр, потом args — этот массив растягивается. Причем args состоит из двух элементов.

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

Неважно, подставляем мы идентификатор или напрямую массив. Принцип одинаковый.

Теперь рассмотрим пример с массивами:

const arr1 = [0, 1, 2]
const arr2 = [3, 4, 5]
arr1.push(...arr2)

// [ 0, 1, 2, 3, 4, 5 ]
console.log(arr1)

push принимает любое количество аргументов. Это хороший способ расширить один массив другим. Для этого мы делаем push и растягиваем второй массив внутри метода. Если мы распечатаем первый массив, то получим один плоский массив:

[0, 1, 2, 3, 4, 5]

Здесь происходит не присоединение последним элементом массива arr2, а каждый элемент из arr2 записывается как отдельный параметр через запятую:

arr1.push(3, 4, 5)

Immutable way

Также spread позволяет работать в неизменяемом виде с формированием новых структур данных. То есть вместо того, чтобы использовать push и изменять объект, мы можем расширить массив — фактически создать новый без изменения старого:

[...iterableObj, 4, 5, 6]

Здесь мы создаем массив и внутри него используем тот же spread. В итоге iterableObj будет преобразован в элементы этого массива.

spread можно поставить в любом месте. И полученное выражение вернет нам новую структуру данных — новый массив без изменения старого.

То же работает и со строчками:

const parts = ['shoulders', 'knees']
const lyrics = ['head', ...parts, 'and', 'toes']

// ["head", "shoulders", "knees", "and", "toes"]
console.log(lyrics)

Immutable Reduce

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

Например, мы можем написать функцию reverse таким образом:

const arr = [1, 2, 3, 4, 5]
arr.reduce((acc, value) => [value, ...acc], [])
// [5, 4, 3, 2, 1]

Здесь мы используем reduce, внутри которого разворачиваем аккумулятор. И мы формируем каждый раз новый массив — добавляем новое значение слева от значений в acc. В итоге мы получаем массив, в котором все элементы в обратном порядке.

При таком подходе каждый раз происходит копирование. В определенных языках это хорошо оптимизировано. Но в JavaScript этот процесс занимает время. Если замерить производительность этого кода с копированием, а не с использованием push, он будет достаточно медленным. Поэтому на больших объемах стоит использовать более оптимизированные варианты.

Если выводить на страницу до ста элементов, это не повлияет на производительность. При этом можно использовать уже готовые функции. Например, фреймворк React или Redux, которые предлагают управлять стейтом в неизменяемом виде.

Рассмотрим еще один пример с дублированием:

const arr = [1, 2, 3, 4, 5]
arr.reduce((acc, value) => [...acc, value, value], [])
// [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ]

Здесь фактически происходит то же самое. При этом задача в том, чтобы продублировать каждый элемент. В итоге мы формируем новый массив, делаем spread и в конце записываем value, value. И на выходе получается массив, в котором каждый элемент продублирован два раза.

Объекты

Spread операцию можно применять не только к массивам, но и к объектам:

const obj = { key: 'value' }
console.log({ ...obj, port: 80 }) // => { key: 'value', port: 80 }

Такой способ позволяет работать с объектами в неизменяемом стиле, что часто используется в коде, например, во фронтенде: https://redux.js.org/recipes/using-object-spread-operator

Если во время применения растягивания повторяются ключи, то в результирующий объект записывается правое значение:

const obj = { key: 'value' }
console.log({ ...obj, key: 'value2', host: 'ya.ru' }) // => { key: 'value2', host: 'ya.ru' }

Эту операцию можно использовать много раз подряд. Например, так можно сливать два объекта в один:

const obj = { key: 'value' }
const obj2 = { key: 'value3', type: 'string' }
console.log({ ...obj, ...obj2 })
// => { key: 'value3', type: 'string' }

Выводы

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

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff