javascript

Введение в часто используемые особенности ES6. Часть 2

  • пятница, 3 ноября 2017 г. в 03:13:25
https://habrahabr.ru/post/341500/
  • JavaScript


Данная публикация является 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

Это все, что касалось деструктуризации объектов. Теперь рассмотрим деструктуризацию массивов.


Деструктуризация массивов


Деструктуризация массивов и объектов аналогичны. При работе с массивами вместо фигурных скобок используются квадратные.


При деструктуризации массива:


  • 1-ая переменная — первый элемент массива
  • 2-ая переменная — второй элемент массива
  • и т.д.

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 параметр и spread оператор похожи, т.к. оба обозначаются тремя точками. Однако они отличаются тем, что выполняют при использовании. По этой причине они по-разному названы, и будут рассмотрены по отдельности.


Rest параметр


В свободной трактовке 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);


ArgumentsSymbol (не массив)


Один из способов посчитать эту сумму аргументов состоит в том, чтобы преобразовать его в массив с помощью 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 оператор


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 привносят три усовершенствования. К ним относятся:


  1. сокращения для значений свойств,
  2. сокращения для методов,
  3. возможность использовать вычисляемые имена свойств.

Рассмотрим каждое из них.


Сокращение для значений свойств


Замечали ли вы, что иногда записываете в свойство объекта переменную с именем, совпадающим с названием свойства объекта? Это показано на следующем примере:


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, которые использую на регулярной основе. Определенно стоит потратить немного своего времени и изучить их, чтобы понимать, что вокруг пишут другие.