В этом уроке мы рассмотрим другую сторону операции 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
. Мы узнали, как выглядит базовое использование этой операции, как она позволяет работать в неизменяемом виде с формированием новых структур данных и как ее можно использовать в функциях высшего порядка.
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.