URL: https://www.progressiverobot.com/understanding-destructuring-rest-parameters-and-spread-syntax-in-javascript-ru/

*Автор выбрал COVID-19 Relief Fund для получения пожертвования в рамках программы Write for DOnations.*

Введение

Многие функции для работы с массивами и объектами были добавлены в язык JavaScript после выпуска спецификации ECMAScript версии 2015. В этой статье мы расскажем о *деструктурировании*, *параметрах rest и* синтаксисе *spread*. Они открывают возможность более прямого доступа к элементам массива или объекта и делают работу с этими структурами данных более быстрой и лаконичной.

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

Деструктурирование

spread illustration for: Деструктурирование

Синтаксис _деструктурирования_ позволяет задавать свойства объектов и элементы массива как переменные. Это значительно сокращает количество строк кода, необходимых для манипулирования данными в этих структурах. Существует два типа деструктурирования: деструктурирование объектов и деструктурирование массивов.

Деструктурирование объектов

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

Рассмотрим пример объекта, представляющего собой заметку со свойствами id, title и date:

				
					
const note = {

 id: 1,

 title: 'My first note',

 date: '01/01/1970',

}

				
			

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

				
					
// Create variables from the Object properties

const id = note.id

const title = note.title

const date = note.date

				
			

С деструктурированием объектов можно уложиться в одну строку. При заключении каждой переменной в фигурные скобки {} JavaScript создаст новые переменные из каждого свойства с тем же именем:

				
					
// Destructure properties into variables

const { id, title, date } = note

				
			

Запустите console.log() для новых переменных:

				
					
console.log(id)

console.log(title)

console.log(date)

				
			

На экран будут выведены начальные значения свойств:

				
					
[secondary_label Output]

1

My first note

01/01/1970

				
			

Примечание. Деструктурирование объекта не изменяет первоначальный объект. Вы все равно можете вызвать первоначальный объект note со всеми исходными записями.

При деструктурировании объектов создаются новые переменные с теми же именами, что и у свойств объекта. Если вы не хотите, чтобы имя новой переменной совпадало с именем свойства, вы можете переименовать новую переменную, используя двоеточие (:) для ввода нового имени, как показано в следующем примере с noteId:

				
					
// Assign a custom name to a destructured value

const { id: noteId, title, date } = note

				
			

Зарегистрируйте новую переменную noteId в консоли:

				
					
console.log(noteId)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

1

				
			

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

				
					
const note = {

 id: 1,

 title: 'My first note',

 date: '01/01/1970',

 author: {

 firstName: 'Sherlock',

 lastName: 'Holmes',

 },

}

				
			

Теперь вы можете деструктурировать объект note, а затем провести деструктурирование еще раз, чтобы создать переменные из свойств объекта author:

				
					
// Destructure nested properties

const {

 id,

 title,

 date,

 author: { firstName, lastName },

} = note

				
			

Затем зарегистрируйте новые переменные firstName и lastName, используя литерали шаблонов:

				
					
console.log(`${firstName} ${lastName}`)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

Sherlock Holmes

				
			

В этом примере у вас есть доступ к содержимому объекта author, но сам объект author остается недоступным. Для доступа к объекту и его вложенным значениям их следует декларировать отдельно:

				
					
// Access object and nested values

const {

 author,

 author: { firstName, lastName },

} = note



console.log(author)

				
			

Этот код выводит объект author:

				
					
[secondary_label Output]

{firstName: "Sherlock", lastName: "Holmes"}

				
			

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

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

				
					
const { length } = 'A string'

				
			

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

				
					
console.log(length)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

8

				
			

Строка A string была косвенно конвертирована в объект для получения свойства length.

Деструктурирование массивов

Деструктурирование массивов позволяет создавтаь новые переменные, используя элементы массива в качестве значения. В качестве примера рассмотрим массив с разными компонентами даты:

				
					
const date = ['1970', '12', '01']

				
			

Массивы в JavaScript гарантированно сохраняют порядок, и поэтому первым индексом всегда будет год, вторым — месяц и т. д. Зная это, вы можете создавать переменные из элементов массива:

				
					
// Create variables from the Array items

const year = date[0]

const month = date[1]

const day = date[2]

				
			

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

				
					
// Destructure Array values into variables

const [year, month, day] = date

				
			

Зарегистрируйте новые переменные в журнале:

				
					
console.log(year)

console.log(month)

console.log(day)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

1970

12

01

				
			

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

				
					
// Skip the second item in the array

const [year, , day] = date



console.log(year)

console.log(day)

				
			

При запуске этого кода будут указаны значения year и day:

				
					
[secondary_label Output]

1970

01

				
			

Вложенные массивы также можно деструктурировать. Вначале создайте вложенный массив:

				
					
// Create a nested array

const nestedArray = [1, 2, [3, 4], 5]

				
			

Затем деструктурируйте массив и зарегистрируйте новые переменные:

				
					
// Destructure nested items

const [one, two, [three, four], five] = nestedArray



console.log(one, two, three, four, five)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

1 2 3 4 5

				
			

Синтаксис деструктурирования можно применять для деструктурирования параметров функции. Для тестирования вам нужно будет деструктурировать ключи и значения из Object.entries().

Вначале декларируйте объект note:

				
					
const note = {

 id: 1,

 title: 'My first note',

 date: '01/01/1970',

}

				
			

Для этого объекта вы можете указать пары ключ-значение, деструктурируя аргументы по мере их передачи в метод forEach()):

				
					
// Using forEach

Object.entries(note).forEach(([key, value]) => {

 console.log(`${key}: ${value}`)

})

				
			

Также вы можете использовать для этой цели цикл for:

				
					
// Using a for loop

for (let [key, value] of Object.entries(note)) {

 console.log(`${key}: ${value}`)

}

				
			

В каждом случае вы получите следующий результат:

				
					
[secondary_label Output]

id: 1

title: My first note

date: 01/01/1970

				
			

Деструктурирование объектов и деструктурирование массивов можно комбинировать в одном выражении деструктурирования. При деструктурировании также можно использовать параметры по умолчанию, как видно из этого примера, где задается дата по умолчанию new Date().

Вначале декларируйте объект note:

				
					
const note = {

 title: 'My first note',

 author: {

 firstName: 'Sherlock',

 lastName: 'Holmes',

 },

 tags: ['personal', 'writing', 'investigations'],

}

				
			

Затем деструктурируйте объект и задайте новую переменную new со значением по умолчанию new Date():

				
					
const {

 title,

 date = new Date(),

 author: { firstName },

 tags: [personalTag, writingTag],

} = note



console.log(date)

				
			

Команда console.log(date) выведет на экран примерно следующее:

				
					
[secondary_label Output]

Fri May 08 2020 23:53:49 GMT-0500 (Central Daylight Time)

				
			

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

Spread

Синтаксис _Spread_ (...) — это еще одно полезное дополнение JavaScript для работы с массивами, объектами и вызовами функций. Spread позволяет распаковывать или раскрывать объекты и элементы итерации (например, массивы) и использовать их для создания копий структур данных с целью упрощения манипуляций с данными.

Spread с массивами

Spread упрощает выполнение распространенных задач с массивами. Допустим, у нас есть два массива и мы хотим их комбинировать:

				
					
// Create an Array

const tools = ['hammer', 'screwdriver']

const otherTools = ['wrench', 'saw']

				
			

Раньше нам нужно было бы использовать concat() для сокращения двух массивов:

				
					
// Concatenate tools and otherTools together

const allTools = tools.concat(otherTools)

				
			

Теперь мы также можем использовать spread для распаковки массивов в новый массив:

				
					
// Unpack the tools Array into the allTools Array

const allTools = [...tools, ...otherTools]



console.log(allTools)

				
			

Результат выполнения будет выглядеть так:

				
					
[secondary_label Output]

["hammer", "screwdriver", "wrench", "saw"]

				
			

Это особенно полезно в случае неизменяемых объектов. Например, вы можете работать с приложением, которое сохранило объект users в массиве объектов:

				
					
// Array of users

const users = [

 { id: 1, name: 'Ben' },

 { id: 2, name: 'Leslie' },

]

				
			

Вы можете использовать push для изменения массива и добавления нового пользователя, если это изменяемый объект:

				
					
// A new user to be added

const newUser = { id: 3, name: 'Ron' }



users.push(newUser)

				
			

Однако при этом изменяется массив user, что может быть для нас нежелательно.

Spread позволяет создать новый массив из существующего и добавить в его конец новый элемент:

				
					
const updatedUsers = [...users, newUser]



console.log(users)

console.log(updatedUsers)

				
			

Теперь в новый массив updatedUsers добавлен новый пользователь, а первоначальный массив users остался без изменений:

				
					
[secondary_label Output]

[{id: 1, name: "Ben"}

 {id: 2, name: "Leslie"}]



[{id: 1, name: "Ben"}

 {id: 2, name: "Leslie"}

 {id: 3, name: "Ron"}]

				
			

Создание копий данных вместо изменения имеющихся данных помогает предотвратить неожиданные изменения. При создании в JavaScript объекта или массива и присвоения его другой переменной вы на самом деле не создаете новый объект, а передаете ссылку.

Рассмотрим этот пример, где мы создаем массив и назначаем его другой переменной:

				
					
// Create an Array

const originalArray = ['one', 'two', 'three']



// Assign Array to another variable

const secondArray = originalArray

				
			

При удалении последнего элемента второго массива изменится первый:

				
					
// Remove the last item of the second Array

secondArray.pop()



console.log(originalArray)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

["one", "two"]

				
			

Spread позволяет делать простую копию массива или объекта, где будут клонированы все свойства верхнего уровня, а вложенные объекты будут передаваться посредством ссылки. Такой простой копии может быть достаточно для простых массивов или объектов.

Если вы напишете тот же код, но при этом скопируете массив с помощью spread, первоначальный массив больше не будет меняться:

				
					
// Create an Array

const originalArray = ['one', 'two', 'three']



// Use spread to make a shallow copy

const secondArray = [...originalArray]



// Remove the last item of the second Array

secondArray.pop()



console.log(originalArray)

				
			

На консоли будет зарегистрировано следующее:

				
					
[secondary_label Output]

["one", "two", "three"]

				
			

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

Создайте новый набор и добавьте в него записи:

				
					
// Create a set

const set = new Set()



set.add('octopus')

set.add('starfish')

set.add('whale')

				
			

Используйте оператор spread с set и зарегистрируйте результаты:

				
					
// Convert Set to Array

const seaCreatures = [...set]



console.log(seaCreatures)

				
			

В результате вы получите следующий вывод:

				
					
[secondary_label Output]

["octopus", "starfish", "whale"]

				
			

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

				
					
const string = 'hello'



const stringArray = [...string]



console.log(stringArray)

				
			

Это даст нам массив, где каждый символ будет элементом массива:

				
					
[secondary_label Output]

["h", "e", "l", "l", "o"]

				
			

Spread с объектами

При работе с объектами spread можно использовать для их копирования и обновления.

Изначально для копирования объектов использовался Object.assign():

				
					
// Create an Object and a copied Object with Object.assign()

const originalObject = { enabled: true, darkMode: false }

const secondObject = Object.assign({}, originalObject)

				
			

Теперь secondObject будет клоном originalObject.

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

				
					
// Create an object and a copied object with spread

const originalObject = { enabled: true, darkMode: false }

const secondObject = { ...originalObject }



console.log(secondObject)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

{enabled: true, darkMode: false}

				
			

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

Spread упрощает добавление и изменение свойств существующего неизменяемого объекта. В этом примере мы добавляем свойство isLoggedIn в объект user:

				
					
const user = {

 id: 3,

 name: 'Ron',

}



const updatedUser = { ...user, isLoggedIn: true }



console.log(updatedUser)

				
			

В результате вы получите следующий вывод:

				
					
[secondary_label Output]

{id: 3, name: "Ron", isLoggedIn: true}

				
			

При обновлении объектов с помощью spread важно учитывать, что каждый вложенный объект также потребуется передать. Рассмотрим пример, когда в объекте user содержится вложенный объект organization:

				
					
const user = {

 id: 3,

 name: 'Ron',

 organization: {

 name: 'Parks & Recreation',

 city: 'Pawnee',

 },

}

				
			

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

				
					
const updatedUser = { ...user, organization: { position: 'Director' } }



console.log(updatedUser)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

id: 3

name: "Ron"

organization: {position: "Director"}

				
			

Если изменяемость неважна, поле можно обновить напрямую:

				
					
user.organization.position = 'Director'

				
			

Однако нам нужно изменяемое решение, и мы используем spread для копирования внутреннего объекта для сохранения имеющихся свойств:

				
					
const updatedUser = {

 ...user,

 organization: {

 ...user.organization,

 position: 'Director',

 },

}



console.log(updatedUser)

				
			

В результате вы получите следующий вывод:

				
					
[secondary_label Output]

id: 3

name: "Ron"

organization: {name: "Parks & Recreation", city: "Pawnee", position: "Director"}

				
			

Spread с вызовами функций

Spread также можно использовать с аргументами в вызовах функций.

Например, у нас имеется функция multiply, которая берет три параметра и умножает их:

				
					
// Create a function to multiply three items

function multiply(a, b, c) {

 return a * b * c

}

				
			

Обычно мы передаем три переменных по отдельности как аргументы вызова функции, примерно так:

				
					
multiply(1, 2, 3)

				
			

Результат будет выглядеть следующим образом:

				
					
[secondary_label Output]

6

				
			

Однако если все значения, которые вы хотите передать функции, уже существуют в массиве, синтаксис syntax позволит использовать каждый элемент массива в качестве аргумента:

				
					
const numbers = [1, 2, 3]



multiply(...numbers)

				
			

Это даст тот же результат:

				
					
[secondary_label Output]

6

				
			

Примечание. Без spread этого можно добиться с помощью apply():

				
					
multiply.apply(null, [1, 2, 3])

				
			

Это даст нам следующее:

				
					
[secondary_label Output]

6

				
			

Теперь вы увидели, как можно использовать spread для сокращения кода и можете рассмотреть другой вариант использовать параметров ... синтаксиса: rest.

Параметры Rest

В последнюю очередь в этой статье мы расскажем о синтаксисе _параметра rest_. Синтаксис аналогичен синтаксису spread (...), но имеет противоположный эффект. Вместо распаковки массива или объекта на отдельные значения синтаксис rest создаст массив с неограниченным количеством аргументов.

Например, если в функции restTest мы захотим использовать массив args, состоящий из неограниченного количества аргументов, мы получим следующее:

				
					
function restTest(...args) {

 console.log(args)

}



restTest(1, 2, 3, 4, 5, 6)

				
			

Все аргументы, переданные в функцию restTest, теперь доступны в массиве args:

				
					
[secondary_label Output]

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

				
			

Синтаксис Rest можно использовать как единственный параметр или как последний параметр в списке. Если его использовать как единственный паарметр, он соберет все аргументы, но в конце списка он соберет все остающиеся аргументы, как показано в этом примере:

				
					
function restTest(one, two, ...args) {

 console.log(one)

 console.log(two)

 console.log(args)

}



restTest(1, 2, 3, 4, 5, 6)

				
			

При этом будут отдельно приниматься два аргумента, а остальные будут сгруппированы в массив:

				
					
[secondary_label Output]

1

2

[3, 4, 5, 6]

				
			

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

				
					
function testArguments() {

 console.log(arguments)

}



testArguments('how', 'many', 'arguments')

				
			

Результат выглядел бы так:

				
					
[secondary_label Output]1

Arguments(3) ["how", "many", "arguments"]

				
			

Однако такой подход имеет ряд недостатков. Во первых, переменную arguments нельзя использовать со стрелочными функциями.

				
					
const testArguments = () => {

 console.log(arguments)

}



testArguments('how', 'many', 'arguments')

				
			

При этом будет возникать ошибка:

				
					
[secondary_label Output]

Uncaught ReferenceError: arguments is not defined

				
			

Кроме того, arguments не является истинным массивом и не может использовать такие методы как map и filter) без предварительной конвертации в массив. Он будет собирать все передаваемые аргументы, а не только остальные аргументы, как показано в примере restTest(one, two, ...args).

Rest можно использовать и при деструктурировании массивов:

				
					
const [firstTool, ...rest] = ['hammer', 'screwdriver', 'wrench']



console.log(firstTool)

console.log(rest)

				
			

Это даст нам следующее:

				
					
[secondary_label Output]

hammer

["screwdriver", "wrench"]

				
			

Также Rest можно использовать при деструктурировании объектов:

				
					
const { isLoggedIn, ...rest } = { id: 1, name: 'Ben', isLoggedIn: true }



console.log(isLoggedIn)

console.log(rest)

				
			

Результат будет выглядеть так:

				
					
[secondary_label Output]

true

{id: 1, name: "Ben"}

				
			

Таким образом, синтаксис rest дает эффективные методы для сбора неопределенного количества элементов.

Заключение

В этой статье мы рассказали о деструктурировании, синтаксисе spread и параметрах rest. Краткое содержание:

  • Деструктурирование используется для создания переменных из элементов массива или свойств объекта.
  • Синтаксис Spread используется для распаковки элементов с итерацией, таких как массивы, объекты и вызовы функций.
  • Синтаксис параметра Rest создает массив из неограниченного количества значений.

Деструктурирование, параметры rest и синтаксис spread — полезные функции JavaScript, позволяющие сохранять код лаконичным и чистым.

Если вы хотите увидеть деструктурирование в действии, пройдите обучающий модуль «Настройка компонентов React с помощью Props», где этот синтаксис используется для деструктурирования данных и их передачи компонентам клиентской части. Если вы хотите узнать больше о JavaScript, вернитесь на страницу серии статей по программированию на JavaScript.