Некоторые языки программирования содержат операторы, которые встречаются достаточно редко, присутствуют не в каждом языке или используют разный синтаксис. В этой статье мы рассмотрим два подобных оператора:
Приступим!
Оператор spread является единственным недооцененным оператором JavaScript, однако разобравшись в его особенностях, вы приобретете очень мощный инструмент в свой арсенал.
Рассмотрим документацию из MDN:
С помощью синтаксиса Spread можно расширить итерацию, например, выражение массива или строку, там, где ожидается ноль или более аргументов (для вызовов функций) или элементов (для литералов массива), или расширить выражение объекта там, где ожидается ноль или большее количество пар ключ-значение (для литералов объекта).
Переформулируем для лучшего понимания:
С помощью синтаксиса Spread можно расширить элементы, сгруппированные внутри определенного контейнера в данный момент, и назначить их другому контейнеру. Совместимые контейнеры включают: массивы, строки, объекты и другие итерируемые элементы (такие как Maps, Sets, TypedArrays и т. д.), а их элементы можно расширить в качестве аргументов функции, элементов массива или пар ключ-значение.
Переформулируем для лучшего понимания:
Теперь рассмотрим несколько кратких примеров для закрепления понятия данных механизмов.
let myArray1 = [1,2,3]
let myString = "Hey planet!"
let myObject = {
name: "Fernando Doglio",
age: 35,
country: "Uruguay",
[Symbol.iterator]: function* () { //мы делаем объект итерируемым, чтобы его можно было расширить
yield myObject.name
yield myObject.age
yield myObject.country
}
}
function test() {
console.log(arguments)
}
let splitLetters = [...myString] //myString.split() больше не нужно использовать
console.log(splitLetters)
//[ 'H', 'e', 'y', ' ', 'p', 'l', 'a', 'n', 'e', 't', '!' ]
let objLetters = {...myString}
console.log(objLetters)
/*
{ '0': 'H',
'1': 'e',
'2': 'y',
'3': ' ',
'4': 'p',
'5': 'l',
'6': 'a',
'7': 'n',
'8': 'e',
'9': 't',
'10': '!' }
*/
test(...myString)
/*
[Arguments] {
'0': 'H',
'1': 'e',
'2': 'y',
'3': ' ',
'4': 'p',
'5': 'l',
'6': 'a',
'7': 'n',
'8': 'e',
'9': 't',
'10': '!' }
*/
//то же самое
test.call(null, ...myArray1)
//[Arguments] { '0': 1, '1': 2, '2': 3 }
test.apply(null, myArray1)
//[Arguments] { '0': 1, '1': 2, '2': 3 }
let objValues = [...myObject] //если объект является итерируемым, то он может заменить Object.values(myObject)
console.log(objValues)
//[ 'Fernando Doglio', 35, 'Uruguay' ]
let {name, age} = {...myObject} //разбиение свойств на отдельные переменные
console.log("Name::", name, " age::", age)
//Name:: Fernando Doglio age:: 35
test(...myObject) //превращение объекта в 3 разных аргумента
//[Arguments] { '0': 'Fernando Doglio', '1': 35, '2': 'Uruguay' }
Здесь есть несколько интересных моментов:
string.split()
. В качестве дополнительного преимущества можно решить, каким образом получить результат разделения: в массиве, объекте или в формате аргументов.Function.call
устраняет необходимость использования метода Function.apply
. *Примечание: простое расширение массива в качестве части обычного вызова функции устраняет необходимость использования их обоих.Рассмотрим ряд более продвинутых и полезных примеров с оператором spread:
let array1 = [1,2,3,4]
//Копирование массива
let copyArray = [...array1]
copyArray.push(4)
console.log(array1)
console.log(copyArray)
/*
[ 1, 2, 3, 4 ]
[ 1, 2, 3, 4, 4 ]
*/
//**ПРЕДУПРЕЖДЕНИЕ*/
let otherArray = [[1], [2], [3]]
copyArray = [...otherArray]
copyArray[0][0] = 3
console.log(otherArray)
console.log(copyArray)
/*
Spread делает неглубокую копию
[ [ 3 ], [ 2 ], [ 3 ] ]
[ [ 3 ], [ 2 ], [ 3 ] ]
*/
//Array concats
let array2 = ['a', 'b', 'c']
let result = [...array1, ...array2]
console.log(result)
//[ 1, 2, 3, 4, 'a', 'b', 'c' ]
//**ПРЕДУПРЕЖДЕНИЕ */
let myString = "hello world"
let result2 = [...array1, ...myString] //totally valid but...
console.log(result2)
//[ 1, 2, 3, 4, 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' ]
result2 = array1.concat(myString) //maybe this is what you really want
console.log(result2)
//[ 1, 2, 3, 4, 'hello world' ]
result2 = [...array1, ...array2, myString] //or this is valid too
console.log(result2)
//[ 1, 2, 3, 4, 'a', 'b', 'c', 'hello world' ]
//Слияние объектов
let myObj1 = {
name: "Fernando Doglio",
age: 34
}
let myObj2 = {
name: "Fernando Doglio",
age: 35,
country: "Uruguay"
}
let mergedObj = {...myObj1, ...myObj2}
console.log(mergedObj)
// { name: 'Fernando Doglio', age: 35, country: 'Uruguay' }
//Удаление повторяющихся элементов из массива
let myArray3 = [1,2,3,4,4,4,4,2,3,3,4,6]
let mySet = new Set(myArray3)
myArray3 = [...mySet]
console.log(myArray3)
//[ 1, 2, 3, 4, 6 ]
Основные моменты:
concat
оператором spread, поскольку они проявляют различное поведение со своими значениями. При этом расширенная версия конкатенации массивов (при правильном выполнении) гораздо более декларативна, чем версия вызова метода.Set
был добавлен в язык, мы все плакали от радости (ну, по крайней мере, я так делал!). Но осознав, что метод Set.values
не возвращает плоский массив, я захотел заплакать снова. Теперь больше не нужно повторять этот результат, можно просто расширить набор в массив и забыть о нем.Мы закончили с оператором spread, и я надеюсь, что приведенные выше примеры предоставили достаточно информации для понимания его особенностей. Теперь переходим к теме деструктуризации и ее значения для синтаксиса и кода.
Деструктуризация — еще одна интересная функция в JavaScript . С помощью этого синтаксиса можно распаковывать значения из объектов и массивов в отдельные свойства. Несмотря на то, что деструктуризация — сама по себе полезная функция, ее также можно сочетать с оператором spread для получения более интересных результатов.
Основанные на списках функции в таких языках, как Perl или Python, предоставляют множество преимуществ. При выполнении следующих действий можно прочувствовать настоящую мощь:
a = 1
b = 2
a, b = b, a
Разве вы не хотели выполнить подобное с помощью JavaScript? А как насчет возврата более одного значения из функции? Раньше приходилось возвращать массив или объект с упакованными в него значениями, и, конечно же, выполнять с ними соответствующие действия в дальнейшем.
В принципе, не существует простого способа написания универсальной функции, которая возвращает несколько значений без выполнения каких-либо компромиссов на синтаксическом или семантическом уровне.
Деструктуризация предоставляет решение для этой проблемы и более того, синтаксис довольно прост. Рассмотрим подробнее:
//замена значений
let a = 1
let b = 2
[a, b] = [b, a]
//несколько возвращаемых значений
function fn() {
return [1,2,4]
}
[a,b,c] = fn()
/*
a = 1
b = 2
c = 4
*/
С помощью нотации массива можно распаковать все имеющиеся с правой стороны значения и назначить их слева. Хотите получить первые два значения из массива, а остальные добавить в другой список? Легко!
let myList = [1,2,3,4,5,6,7]
[first, second, ...tail] = myList
/*
first = 1
second = 2
tail = [3,4,5,6,7]
*/
Как видите, выполнение нескольких присваиваний представляет собой простой процесс. Эта функция особенно полезна при работе с многогрупповыми регулярными выражениями, такими как:
function parseURL(url) {
var parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
if (!parsedURL) {
return [];
}
[, ...parsedValues] = parsedURL // мы игнорируем первый элемент
return parsedValues.map( v => v.length ? v : undefined) //Мы следим за тем, чтобы пустые совпадения были определены в качестве undefined
}[protocol, host, path] = parseURL("https://www.fdoglio.com/blog")
console.log(`The host is -${host}-, the protocol -${protocol}- and you're accessing the path -${path}-`);
В приведенном выше примере деструктуризация используется в двух местах:
parsedURL.shift()
, однако мы стремимся использовать декларативный подход.Также можно установить значения по умолчанию, если правая часть не определена (undefined
).
[protocol, host, path="none"] = parseURL("https://www.fdoglio.com/");
console.log(`The host is -${host}-, the protocol -${protocol}- and you're accessing the path -${path}-`);
//The host is -www.fdoglio.com-, the protocol -https- and you're accessing the path -none-
Примечание: это работает, поскольку мы вручную заменяем пустые совпадения на undefined
в функции parsing, иначе значения по умолчанию будут игнорироваться.
По тем же стандартам можно использовать именованные атрибуты, переданные функциям, и даже значения по умолчанию во время вызовов функций. Например:
let myObject = {
name: "Fernando Doglio",
country: "Uruguay",
age: 35
}
//destructuring
function wishHappyBirthday({name, age, numberOfKids=2}) {
console.log(`Hello ${name} happy ${age}th birthday, have a great day with your wife and ${numberOfKids} kids`)
}
wishHappyBirthday(myObject) //расширение объекта в параметры функции
В этом примере выполняются те же действия, что и с массивами, но с объектами, включая извлечение необходимых свойств и установку значений по умолчанию на случай, если их не существует.
Убедитесь, что используете правильные названия в объявлении функции в соответствии с названиями свойств, поскольку присваивание выполняется не по порядку (как с обычными функциями), а по совпадению названий.
Действия, представленные выше, также можно выполнить с извлечением набора определенных ключей в отдельные переменные. Например:
const student = {
firstname: 'Fernando',
lastname: 'Doglio',
country: 'Uruguay'
};
//pull properties by name
let { firstname, lastname } = student
console.log(`Nice to meet you ${firstname} ${lastname}!`)
//присваивание свойств определенным названиям переменных
let { firstname: primerNombre, lastname: apellido} = student
console.log(primerNombre, apellido);
Первый пример довольно прост: два определенных свойства извлекаются из объекта, оставив country
. Однако во втором примере показано, как переназначить содержимое свойства в новую переменную (в случае, если название уже занято или если необходимо установить больший контроль).
Деструктуризация и оператор spread не получили широкого распространения. Я надеюсь, что вы перейдете на более декларативный стиль программирования и начнете использовать эти новые инструменты языка.
Перевод статьи Fernando Doglio: Spread and Destructuring: A How-To Guide for JavaScripters
Комментарии