[Перевод] ES6: полезные советы и неочевидные приёмы
Стандарт EcmaScript 2015 (ES6) существует уже несколько лет. Он принёс с собой множество новых возможностей, разные способы использования которых далеко не всегда очевидны. Вот обзор некоторых из этих возможностей с примерами и комментариями.
1. Обязательные параметры функции
ES6 позволяет задавать значения формальных параметров по умолчанию, что позволяет, при вызове функции без указания значений этих параметров, подставлять их стандартные значения. Это позволяет задавать параметры, без передачи которых функция работать не будет.
В следующем примере мы задаём функцию 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 объекта Array
чрезвычайно универсален. Обычно его используют для преобразования массива неких элементов к единственному значению. Однако с его помощью можно сделать ещё очень много всего полезного.
Обратите внимание на то, что в следующих примерах мы полагаемся на то, что исходное значение переменной — это либо массив, либо объект, а не нечто вроде строки или числа.
▍2.1. Использование reduce для одновременного выполнения мэппинга и фильтрации массива
Представим, что перед нами стоит следующая задача. Имеется список элементов, каждый из которых надо модифицировать (что сводится к использованию метода map), после чего отобрать из него несколько элементов (это можно сделать с помощью метода filter). Эта задача вполне решаема последовательным применением методов 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
на этом не ограничиваются. Если вам интересны подробности об этом полезном методе — изучите примеры с этой страницы.
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). Коллекции могут содержать только уникальные значения.
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 — просим ими поделиться.