Введение в часто используемые особенности ES6. Часть 2
- пятница, 3 ноября 2017 г. в 03:13:25
Данная публикация является 2-ой частью перевода статьи «Introduction to commonly used ES6 features» под авторством Zell Liew, размещенного здесь. Перевод 1-ой части находится здесь.
Деструктуризация — удобный способ извлечения значений из массивов и объектов. Между деструктуризацией массивов и объектов существуют незначительные различия, поэтому рассмотрим их отдельно.
Допустим, имеется следующий объект:
const Zell = {
firstName: 'Zell',
lastName: 'Liew'
}
Для получения firstName
и lastName
из Zell
необходимо создать две переменные, а затем присвоить каждой переменной значение следующим образом:
let firstName = Zell.firstName; // Zell
let lastName = Zell.lastName; // Liew
С деструктуризацией создание и присвоение этих переменных производится одной единственной строкой кода. Ниже пример деструктуризации объекта:
let { firstName, lastName } = Zell;
console.log(firstName); // Zell
console.log(lastName); // Liew
При добавлении фигурных скобок к объявлению переменных конструируется указание создать эти переменные и затем присвоить Zell.firstName
в firstName
, а Zell.lastName
в lastName
.
Поясним, что происходит под «капотом»:
// То, что вы пишите:
let { firstName, lastName } = Zell;
// То, что ES6 выполняет:
let firstName = Zell.firstName;
let lastName = Zell.lastName;
Теперь если имя переменной уже занято, то невозможно объявить такую же переменную снова (особенно если вы используете let
или const
).
Следующий код не сработает:
let name = 'Zell Liew';
let course = {
name: 'JS Fundamentals for Frontend Developers'
// ... другие свойства
}
let { name } = course; // Uncaught SyntaxError: имя идентификатора уже было объявлено
В таких ситуациях можно переименовывать переменные одновременно с деструктуризацией с помощью двоеточия.
В примере ниже создается переменная courseName
и ей присваивается course.name
.
let { name: courseName } = course;
console.log(courseName); // JS Fundamentals for Frontend Developers
// То, что ES6 выполняет:
let courseName = course.name;
Обратим внимание на то, что если деструктурировать переменную, которая отсутствует в объекте, то будет возвращено undefined
.
let course = {
name: 'JS Fundamentals for Frontend Developers'
}
let { package } = course;
console.log(package); // undefined
Помните параметры по умолчанию? Также можно записывать параметры по умолчанию для деструктурированных переменных. Синтаксис похож на синтаксис объявления функций.
let course = {
name: 'JS Fundamentals for Frontend Developers'
}
let { package = 'full course' } = course;
console.log(package); // full course
Вы даже можете переименовывать переменные при указании параметров по умолчанию. Необходимо составить комбинацию из двух синтаксисов, что сначала будет необычно, но к этому можно привыкнуть:
let course = {
name: 'JS Fundamentals for Frontend Developers'
}
let { package: packageName = 'full course' } = course;
console.log(packageName); // full course
Это все, что касалось деструктуризации объектов. Теперь рассмотрим деструктуризацию массивов.
Деструктуризация массивов и объектов аналогичны. При работе с массивами вместо фигурных скобок используются квадратные.
При деструктуризации массива:
let [one, two] = [1, 2, 3, 4, 5];
console.log(one); // 1
console.log(two); // 2
Если при деструктуризации количество переменных превышает размер массива, дополнительные переменные будут undefined
.
let [one, two, three] = [1, 2];
console.log(one); // 1
console.log(two); // 2
console.log(three); // undefined
При деструктуризации массива часто извлекаются только необходимые переменные. Для получения остатка используется rest оператор следующим способом:
let scores = ['98', '95', '93', '90', '87', '85'];
let [first, second, third, ...rest] = scores;
console.log(first); // 98
console.log(second); // 95
console.log(third); // 93
console.log(rest); // [90, 87, 85]
Rest оператор будет подробнее представлен в следующей части. Сейчас же рассмотрим перестановку переменных с помощью деструктуризации массива.
Допустим, имеются две переменные a
и b
.
let a = 2;
let b = 3;
Вам необходимо переставить эти переменные так, чтобы a
стало равно 3 и b
равно 2. В ES5 вы бы использовали временную третью переменную для решения этой задачи:
let a = 2;
let b = 3;
let temp;
// перестановка
temp = a; // в переменной temp число 2
a = b; // в переменной a число 3
b = temp; // в переменной b число 2
Такой код работает, несмотря на неясную и сбивающую с толку логику с включением третьей переменной.
С деструктуризацией массивов в ES6 это решается следующим способом:
let a = 2;
let b = 3;
// перестановка деструктуризацией массива
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 2
Такой способ перестановки переменных намного проще предыдущего.
Далее рассмотрим деструктуризацию массивов и объектов в функциях.
Деструктуризация может использоваться буквально везде, даже при объявлении функций.
Допустим, имеется функция, которая принимает массив значений и возвращает объект с тремя верхними значениями. Такая функция аналогия того, что происходит при деструктуризации массивов.
function topThree (scores) {
let [first, second, third] = scores;
return {
first: first,
second: second,
third: third
}
}
Альтернативный способ записи такой функции заключается в деструктуризации scores
при объявлении функции. В этом случае потребуется написать на одну строку кода меньше. Также необходимо помнить, что в функцию будет передаваться массив.
function topThree ([first, second, third]) {
return {
first: first,
second: second,
third: third
}
}
Если мы можем комбинировать параметры по умолчанию и деструктуризацию при объявлении функций, то, что будет результатом кода ниже?
function sayMyName ({
firstName = 'Zell',
lastName = 'Liew'
} = {}) {
console.log(firstName + ' ' + lastName);
}
Во-первых, заметим, что функция принимает один аргумент-объект. Этот объект необязательный и по умолчанию равен {}
.
Во-вторых, происходит попытка деструктурировать переменные firstName
и lastName
из переданного объекта. Если такие свойства будут найдены, то они будут использованы.
В итоге если firstName
или lastName
не будут определены (undefined
) в объекте, то им присвоятся значения Zell
и Liew
, соответственно.
Итак, такая функция выводит следующие результаты:
sayMyName(); // Zell Liew
sayMyName({firstName: 'Zell'}); // Zell Liew
sayMyName({firstName: 'Vincy', lastName: 'Zhang'}); // Vincy Zhang
Далее рассмотрим rest и spread.
Rest параметр и spread оператор похожи, т.к. оба обозначаются тремя точками. Однако они отличаются тем, что выполняют при использовании. По этой причине они по-разному названы, и будут рассмотрены по отдельности.
В свободной трактовке rest параметр указывает взять остаток данных и обернуть его в массив. Если детально, то происходит преобразование списка разделенных запятыми аргументов в массив.
Ознакомимся с rest параметром в действии. Допустим, имеется функция add
, которая суммирует свои аргументы:
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // 55
В ES5 мы зависим от переменной arguments
каждый раз, когда имеем дело с функцией, которая принимает неизвестное количество переменных. Переменная arguments
— массивоподобный Symbol
.
function sum () {
console.log(arguments);
}
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Arguments
— Symbol
(не массив)
Один из способов посчитать эту сумму аргументов состоит в том, чтобы преобразовать его в массив с помощью Array.prototype.slice.call(arguments
), а затем циклом пройти по каждому элементу такими методами массивов как forEach
или reduce
.
Уверен, forEach
можете сами реализовать, поэтому ниже приведен пример с reduce
:
// ES5
function sum () {
let argsArray = Array.prototype.slice.call(arguments);
return argsArray.reduce(function(sum, current) {
return sum + current;
}, 0)
}
В ES6 c rest параметром предоставлена возможность перевести разделённые запятыми аргументы прямо в массив:
// ES6
const sum = (...args) => args.reduce((sum, current) => sum + current, 0);
// ES6 без сильного сокращения кода с помощью стрелочных функций
function sum (...args) {
return args.reduce((sum, current) => sum + current, 0);
}
Rest параметр был ранее коротко представлен в разделе про деструктуризацию. Тогда из массива были деструктурированы верхние три значения:
let scores = ['98', '95', '93', '90', '87', '85'];
let [first, second, third] = scores;
console.log(first); // 98
console.log(second); // 95
console.log(third); // 93
При необходимости получить остальные данные мы бы обратились к rest параметру.
let scores = ['98', '95', '93', '90', '87', '85'];
let [first, second, third, ...restOfScores] = scores;
console.log(restOfScores); // [90, 97, 95]
Если вас что-то сбило с толку, то запомните, что rest параметр переводить данные в массив и появляется в параметрах функций и при деструктуризации массивов.
Далее рассмотрим spread оператор.
Spread оператор действует противоположно rest параметру. В свободной трактовке оператор принимает массив и разворачивает его в разделенный запятыми список аргументов.
let array = ['one', 'two', 'three'];
// Ниже строки кода равнозначны
console.log(...array); // one two three
console.log('one', 'two', 'three'); // one two three
Spread оператор часто используется для конкатенации массивов простым для чтения и понимания способом.
Допустим, необходимо объединить следующие массивы:
let array1 = ['one', 'two'];
let array2 = ['three', 'four'];
let array3 = ['five', 'six'];
В ES5 для конкатенации массивов используется метод Array.concat
. Для объединения множества массивов составляется цепь следующим образом:
// ES5
let combinedArray = array1.concat(array2).concat(array3);
console.log(combinedArray) // ['one', 'two', 'three', 'four', 'five', 'six'];
В ES6 spread оператор позволяет объединять массивы в новый массив способом, который легче для чтения:
// ES6
let combinedArray = [...array1, ...array2, ...array3];
console.log(combinedArray); // ['one', 'two', 'three', 'four', 'five', 'six']
Spread оператор также может применяться для удаления элемента из массива без видоизменения массива. Этот метод распространен в Redux. Рекомендую вам посмотреть видео от Dan Abramov, чтобы разобраться, как это работает.
Расширенные литералы объектов в ES6 привносят три усовершенствования. К ним относятся:
Рассмотрим каждое из них.
Замечали ли вы, что иногда записываете в свойство объекта переменную с именем, совпадающим с названием свойства объекта? Это показано на следующем примере:
const fullName = 'Zell Liew';
const Zell = {
fullName: fullName
}
В таких ситуациях вам бы хотелось писать код короче?
ES6 расширяет объекты сокращениями для значений свойств, что означает возможность писать только переменную, если имя переменной совпадает с именем свойства.
Это выглядит следующим образом:
const fullName = 'Zell Liew';
// ES6
const Zell = {
fullName
}
// То, что выполняет ES6:
const Zell = {
fullName: fullName
}
Методами являются функции, связанные со свойством объекта. Ниже пример метода:
const anObject = {
aMethod: function () { console.log("I'm a method!~~")}
}
Сокращениями для методов в ES6 является то, что удаление : function
из объявления метода не нарушает его работу:
const anObject = {
// ES6
aShorthandMethod (arg1, arg2) {},
// ES5
aLonghandMethod: function (arg1, arg2) {},
}
С этим усовершенствованием объекты уже получают сокращение для метода, поэтому не рекомендую использовать стрелочные функции при объявлении объектов, т.к. это нарушит контекст this
(вернитесь к разделу по стрелочным функциям, если не помните, почему так происходит).
const dontDoThis = {
// Не следует так писать:
arrowFunction: () => {}
}
Теперь перейдем к последнему усовершенствованию объектов.
Иногда возникает необходимость в динамическом имени свойства при создании объектов. Раньше вам бы пришлось создать объект, а затем внести свойство следующим способом:
// ES5
const newPropertyName = 'smile';
// Сначала создать объект
const anObject = { aProperty: 'a value' }
// Затем записать свойство
anObject[newPropertyName] = ':D';
// Добавление немного другого свойства в объект
anObject['bigger ' + newPropertyName] = 'XD';
// Результат
// {
// aProperty: 'a value',
// 'bigger smile': 'XD'
// smile: ':D',
// }
В ES6 нет необходимости в таком «обходном пути», т.к. существует возможность назначить динамическое имя свойства напрямую при создании объекта. В тоже время важно обернуть динамическое свойство в квадратные скобки:
const newPropertyName = 'smile';
// ES6
const anObject = {
aProperty: 'a value',
// Динамические названия свойств
[newPropertyName]: ':D',
['bigger ' + newPropertyName]: 'XD',
}
// Результат
// {
// aProperty: 'a value',
// 'bigger smile': 'XD'
// smile: ':D',
// }
Это всё, что касалось расширенных литералов объектов. Далее рассмотрим другую полезную особенность: шаблонные строки.
Работа со строками в JavaScript крайне тяжелый процесс. Мы сталкивались с этим при создании функции announcePlayer в разделе про параметры по умолчанию. Ниже в коде созданы пробелы с пустыми строками, которые объединены сложением:
function announcePlayer (firstName, lastName, teamName) {
console.log(firstName + ' ' + lastName + ', ' + teamName);
}
В ES6 эта проблема решается шаблонными литералами (в спецификации они ранее назывались шаблонными строками).
Для создания шаблонного литерала необходимо обернуть строки в обратные апострофы. Внутри обратных апострофов используется специальный указатель ${}
, в котором можно писать JavaScript код.
Ниже пример того, как это выглядит в действии:
const firstName = 'Zell';
const lastName = 'Liew';
const teamName = 'unaffiliated';
const theString = `${firstName} ${lastName}, ${teamName}`;
console.log(theString);
// Zell Liew, unaffiliated
Таким образом, появилось возможность составлять различные комбинации с помощью шаблонных литералов, что напоминает использование шаблонизатора.
Самой полезной особенностью шаблонных литералов является возможность создания многострочных строк. Ниже представлен пример:
const multi = `One upon a time,
In a land far far away,
there lived a witich,
who could change night into day`;
Такие строки могут быть использованы для создания HTML элементов в JavaScript коде (это не лучший способ создания HTML элементов, но лучше, чем их создание один за одним по отдельности).
const container = document.createElement('div');
const aListOfItems =
`<ul>
<li>Point number one</li>
<li>Point number two</li>
<li>Point number three</li>
<li>Point number four</li>
</ul>`;
container.innerHTML = aListOfItems;
document.body.append(container);
Другой особенностью шаблонных литералов являются теги. Теги — это функции, которые позволяют манипулировать шаблонным литералом для замены любой строки.
Ниже пример:
const animal = 'lamb';
// Ниже тег
const tagFunction = () => {
// ваш код
}
// Функция tagFunction позволяет манипулировать шаблонным литералом
const string = tagFunction `Mary had a little ${animal}`;
Если быть честным, то несмотря на то, что шаблонные теги производят впечатление, у меня до сих пор не возникло ситуации для их применения. С целью их более детального изучения рекомендую прочитать материал на MDN.
Это были все особенности ES6, которые использую на регулярной основе. Определенно стоит потратить немного своего времени и изучить их, чтобы понимать, что вокруг пишут другие.