- PVSM.RU - https://www.pvsm.ru -

ES6: полезные советы и неочевидные приёмы

Стандарт EcmaScript 2015 (ES6) существует уже несколько лет. Он принёс с собой множество новых возможностей, разные способы использования которых далеко не всегда очевидны. Вот обзор некоторых из этих возможностей с примерами и комментариями.

image [1]

1. Обязательные параметры функции

ES6 позволяет задавать [2] значения формальных параметров по умолчанию, что позволяет, при вызове функции без указания значений этих параметров, подставлять их стандартные значения. Это позволяет задавать параметры, без передачи которых функция работать не будет.

В следующем примере мы задаём функцию required() как значение по умолчанию для параметров a и b. Это означает, что если a или b не будут переданы функции при вызове, будет вызвана функция required() и мы получим сообщение об ошибке.

const required = () => {throw new Error('Missing parameter')};
//При вызове этой функции произойдёт ошибка, если параметры "a" или "b" не заданы
const add = (a = required(), b = required()) => a + b;
add(1, 2) //3
add(1) // Error: Missing parameter.

2. Секреты метода reduce

Метод reduce [3] объекта Array чрезвычайно универсален. Обычно его используют для преобразования массива неких элементов к единственному значению. Однако с его помощью можно сделать ещё очень много всего полезного.

Обратите внимание на то, что в следующих примерах мы полагаемся на то, что исходное значение переменной — это либо массив, либо объект, а не нечто вроде строки или числа.

▍2.1. Использование reduce для одновременного выполнения мэппинга и фильтрации массива

Представим, что перед нами стоит следующая задача. Имеется список элементов, каждый из которых надо модифицировать (что сводится к использованию метода map [4]), после чего отобрать из него несколько элементов (это можно сделать с помощью метода filter [5]). Эта задача вполне решаема последовательным применением методов map и filter, но так по списку элементов придётся пройтись дважды. А нас это не устраивает.

В следующем примере нужно удвоить значение каждого элемента в массиве, после чего — отобрать лишь те из них, которые больше 50. Обратите внимание на то, как мы можем использовать мощный метод reduce и для удвоения, и для фильтрации элементов. Это очень эффективно.

const numbers = [10, 20, 30, 40];
const doubledOver50 = numbers.reduce((finalList, num) => {
  
  num = num * 2; //удвоить каждое число (аналог map)
  
  //отобрать числа > 50 (аналог filter)
  if (num > 50) {
    finalList.push(num);
  }
  return finalList;
}, []);
doubledOver50; // [60, 80]

▍2.2. Использование reduce вместо map или filter

Если вы проанализировали вышеприведённый пример, то для вас окажется совершенно очевидной возможность использования метода reduce вместо map или filter.

▍2.3. Использование reduce для анализа расстановки скобок

Вот ещё один пример полезных возможностей метода reduce.

Предположим, что у нас имеется строка со скобками, и нам нужно узнать, сбалансированы они, то есть, во-первых, равно ли количество открывающих скобок скобкам закрывающим, и, во-вторых, то, что соответствующие открывающие скобки находятся перед закрывающими.

Эту задачу можно решить с помощью метода reduce так, как показано ниже. Тут мы используем переменную counter с начальным значением, равным 0. Мы увеличиваем её значение на единицу, если находим символ «(», и уменьшаем, если находим символ «)». Если скобки в строке сбалансированы, на выходе должен получиться 0.

//Возвращает 0 если скобки сбалансированы.
const isParensBalanced = (str) => {
  return str.split('').reduce((counter, char) => {
    if(counter < 0) { //matched ")" before "("
      return counter;
    } else if(char === '(') {
      return ++counter;
    } else if(char === ')') {
      return --counter;
    }  else { //найден какой-то другой символ
      return counter;
    }
    
  }, 0); //<-- начальное значение для счётчика
}
isParensBalanced('(())') // 0 <-- скобки сбалансированы
isParensBalanced('(asdfds)') //0 <-- скобки сбалансированы
isParensBalanced('(()') // 1 <-- скобки несбалансированы
isParensBalanced(')(') // -1 <-- скобки несбалансированы

▍2.4. Подсчёт количества совпадающих значений массива (преобразование массива в объект)

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

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

var cars = ['BMW','Benz', 'Benz', 'Tesla', 'BMW', 'Toyota'];
var carsObj = cars.reduce(function (obj, name) { 
   obj[name] = obj[name] ? ++obj[name] : 1;
  return obj;
}, {});
carsObj; // => { BMW: 2, Benz: 2, Tesla: 1, Toyota: 1 }

На самом деле, возможности reduce на этом не ограничиваются. Если вам интересны подробности об этом полезном методе — изучите примеры с этой [6] страницы.

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

▍3.1. Удаление ненужных свойств

Бывает так, что из объекта требуется удалить ненужные свойства. Возможно, это свойства, которые содержат секретные сведения, возможно — они слишком велики. Вместо того, чтобы перебирать весь объект и удалять подобные свойства, можно извлечь эти свойства в переменные и оставить нужные свойства в rest-параметре.

В следующем примере нам нужно избавиться от свойств _internal и tooBig. Их можно присвоить переменным с такими же именами и сохранить оставшиеся свойства в rest-параметре cleanObject, который можно использовать позже.

let {_internal, tooBig, ...cleanObject} = {el1: '1', _internal:"secret", tooBig:{}, el2: '2', el3: '3'};
console.log(cleanObject); // {el1: '1', el2: '2', el3: '3'}

▍3.2. Деструктурирование вложенных объектов в параметрах функции

В следующем примере свойство engine — это вложенный объект объекта car. Если нам нужно узнать, скажем, свойство vin объекта engine, его легко можно деструктурировать.

var car = {
  model: 'bmw 2018',
  engine: {
    v6: true,
    turbo: true,
    vin: 12345
  }
}
const modelAndVIN = ({model, engine: {vin}}) => {
  console.log(`model: ${model} vin: ${vin}`);
}
modelAndVIN(car); // => model: bmw 2018  vin: 12345

▍3.3. Слияние объектов

ES6 поддерживает оператор расширения (spread), выглядящий как три точки. Обычно его применяют для работы с массивами, но его можно использовать и с обычными объектами.

В следующем примере мы используем оператор расширения для формирования нового объекта из двух существующих. Ключи свойств объекта №2 переопределят ключи свойств объекта №1. В частности, свойства b и c из object2 переопределят такие же свойства объекта object1.

let object1 = { a:1, b:2,c:3 }
let object2 = { b:30, c:40, d:50}
let merged = {…object1, …object2} //применим оператор расширения для формирования нового объекта
console.log(merged) // {a:1, b:30, c:40, d:50}

4. Коллекции

▍4.1. Удаление повторяющихся значений из массивов

В ES6 можно избавляться от повторяющихся значений, используя коллекции (Set [7]). Коллекции могут содержать только уникальные значения.

let arr = [1, 1, 2, 2, 3, 3];
let deduped = [...new Set(arr)] // [1, 2, 3]

▍4.2. Использование методов массивов с коллекциями

Преобразование коллекций в массивы не сложнее, чем использование вышеупомянутого оператора расширения. Все методы массивов можно использовать и с коллекциями.

Предположим, у нас имеется коллекция, которую нужно отфильтровать так, чтобы в ней содержались только элементы, которые больше 3. Вот как это сделать.

let mySet = new Set([1,2, 3, 4, 5]);
var filtered = [...mySet].filter((x) => x > 3) // [4, 5]

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

Часто бывает так, что функция возвращает множество значений в виде массива. Извлечь эти значения из массива можно с использованием техники деструктурирования.

▍5.1. Обмен значений переменных

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

let param1 = 1;
let param2 = 2;
//поменяем местами значения param1 и param2 
[param1, param2] = [param2, param1];
console.log(param1) // 2
console.log(param2) // 1

▍5.2. Приём и обработка нескольких значений, возвращаемых функцией

В следующем примере мы загружаем некую статью, находящуюся по адресу /post, и связанные с ней комментарии с адреса /comments. Тут применяется конструкция async/await, функция возвращает результат в виде массива.

Деструктурирование упрощает присвоение результатов работы функции соответствующим переменным.

async function getFullPost(){
  return await Promise.all([
    fetch('/post'),
    fetch('/comments')
  ]);
}
const [post, comments] = getFullPost();

Итоги

В этом материале мы рассмотрели несколько простых, но неочевидных приёмов использования новых возможностей ES6. Надеемся, они вам пригодятся.

Уважаемые читатели! Если вы знаете какие-нибудь интересные приёмы использования возможностей ES6 — просим ими поделиться.

Автор: ru_vds

Источник [8]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/276287

Ссылки в тексте:

[1] Image: https://habrahabr.ru/company/ruvds/blog/352128/

[2] задавать: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

[3] reduce: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

[4] map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

[5] filter: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

[6] этой: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

[7] Set: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

[8] Источник: https://habrahabr.ru/post/352128/?utm_campaign=352128