Tutorial

Концепции деструктурирования, параметров Rest и синтаксиса Spread в JavaScript

DevelopmentJavaScript

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

Введение

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

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

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

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

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

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

Рассмотрим пример объекта, представляющего собой заметку со свойствами 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)

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

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)

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

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}`)

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

Output
Sherlock Holmes

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

// Access object and nested values
const {
  author,
  author: { firstName, lastName },
} = note

console.log(author)

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

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

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

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

const { length } = 'A string'

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

console.log(length)

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

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)

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

Output
1970 12 01

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

// Skip the second item in the array
const [year, , day] = date

console.log(year)
console.log(day)

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

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)

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

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}`)
}

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

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) выведет на экран примерно следующее:

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)

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

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 остался без изменений:

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)

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

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)

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

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)

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

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

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

const string = 'hello'

const stringArray = [...string]

console.log(stringArray)

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

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)

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

Output
{enabled: true, darkMode: false}

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

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

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

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

console.log(updatedUser)

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

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)

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

Output
id: 3 name: "Ron" organization: {position: "Director"}

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

user.organization.position = 'Director'

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

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

console.log(updatedUser)

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

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)

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

Output
6

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

const numbers = [1, 2, 3]

multiply(...numbers)

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

Output
6

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

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

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

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:

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)

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

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

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

function testArguments() {
  console.log(arguments)
}

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

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

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

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

const testArguments = () => {
  console.log(arguments)
}

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

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

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)

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

Output
hammer ["screwdriver", "wrench"]

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

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

console.log(isLoggedIn)
console.log(rest)

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

Output
true {id: 1, name: "Ben"}

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

Заключение

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

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

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

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

Creative Commons License