Несмотря на то, что в последние семь лет я пишу на JavaScript почти каждый рабочий день, должен признаться, что уделяю мало внимания сообщениям о нововведениях от ES. Главные возможности вроде async/await и прокси — это одно, но ещё каждый год идёт поток мелких поэтапных изменений, которые не попадают в моё поле зрения, поскольку всегда находится что-то более важное для изучения.
В этой статье я собрал возможности современного JS, о которых мало говорили, когда они появились. Некоторые из них всего лишь повышают удобство, а некоторые невероятно практичны и могут сэкономить написание кучи кода.
ES2015
Двоичные и восьмеричные литералы
В JavaScript не часто приходится пользоваться двоичным манипулированием. Но иногда возникают задачи, которые иначе не решить. Например, когда пишешь высокопроизводительный код для слабых устройств, втискиваешь биты в локальное хранилище, выполняешь пиксельные RGB-манипуляции в браузере или работаешь с тесно упакованными двоичными форматами данных.
Всё это может потребовать большого количества работы по скрытию/объединению двоичных чисел; мне всегда казалось, что их зря упрятали в десятичные. Именно для таких случаев в ES6 добавили формат двоичных литералов:
0b
.const binaryZero = 0b0;
const binaryOne = 0b1;
const binary255 = 0b11111111;
const binaryLong = 0b111101011101101;
Это сильно упрощает работу с двоичными флагами:
// Pizza toppings
const olives = 0b0001;
const ham = 0b0010;
const pineapple = 0b0100;
const artechoke = 0b1000;
const pizza_ham_pineapple = pineapple | ham;
const pizza_four_seasons = olives | ham | artechoke;
То же самое и с восьмеричными числами. В мире JS это нишевая возможность, но организации сетевой работы и некоторых файловых форматах они используются часто. Вы можете писать восьмеричные числа с помощью синтаксиса
0o
.Number.isNaN()
Не путать с
window.isNaN()
, это новый метод с гораздо более интуитивным поведением.У классического
isNaN
есть несколько интересных хитростей:isNaN(NaN) === true
isNaN(null) === false
isNaN(undefined) === true
isNaN({}) === true
isNaN('0/0') === true
isNaN('hello') === true
Что нам это даёт? Во-первых, ни один из этих параметров на самом деле не является
NaN
. Как обычно, проблема во всеми «любимом» свойстве JavaScript: приведении типов. Аргументы для window.isNaN
приведены к числам с помощью функции Number
.Эту проблему решает новый статический метод
Number.isNaN()
. Он раз и навсегда возвращает равенство аргументов, переданных ему и NaN
. Это абсолютно однозначно:Number.isNaN(NaN) === true
Number.isNaN(null) === false
Number.isNaN(undefined) === false
Number.isNaN({}) === false
Number.isNaN('0/0') === false
Number.isNaN('hello') === false
Сигнатура:
Number.isNaN : (value: any) => boolean
ES2016
Оператор возведение в степень
Время от времени такое случается, так что хорошо иметь под рукой литеральный синтаксис для возведения в степень:
2**2 === 4
3**2 === 9
3**3 === 27
Странно, но я был уверен, что в JavaScript такое уже есть. Возможно, спутал с Python.
Array.prototype.includes()
Такое трудно было пропустить, но если в последние три года вы писали
array.indexOf(x) !== -1
, то возрадуйтесь новому методу includes
:[1, 2, 3].includes(2) === true
[1, 2, 3].includes(true) === false
includes
использует алгоритм Same Value Zero, который почти идентичен проверке на строгое равенство (===
), за исключением того, что может обрабатывать значения NaN
. Этот алгоритм тоже сравнивает объекты по ссылкам, а не содержимому:const object1 = {};
const object2 = {};
const array = [object1, 78, NaN];
array.includes(object1) === true
array.includes(object2) === false
array.includes(NaN) === true
includes
может брать второй параметр, fromIndex
, который позволяет вам предоставлять значение сдвига:// positions 0 1 2 3 4
const array = [1, 1, 1, 2, 2];
array.includes(1, 2) === true
array.includes(1, 3) === false
Полезно.
Сигнатура:
Array.prototype.includes : (match: any, offset?: Int) => boolean
ES2017
Разделяемые память и атомарные операции
Это пара замечательных возможностей, которые просто бесценны, если нужно выполнить много работы с помощью веб-воркеров. Вы можете напрямую делиться памятью с несколькими процессами и задавать блокировки, чтобы избежать состояния гонки.
Это две большие возможности с довольно сложными API, так что здесь я их описывать не буду. За подробностями отправляю вас к этой статье: https://www.sitepen.com/blog/the-return-of-sharedarraybuffers-and-atomics/. Еще не все браузеры поддерживают эти функции, но надеюсь, что в ближайшие пару лет ситуация улучшится.
ES2018
Золотая жила регулярных выражений
В ES2018 появилась целая россыпь новых возможностей регулярных выражений:
Lookbehind-сопоставления (сопоставление с предыдущими символами)
В тех средах исполнения, которые это поддерживают, вы теперь можете писать регулярные выражения, которые ищут символы до того, что вы сопоставляете. Например, чтобы найти все числа, перед которыми стоит знак доллара:
const regex = /(?<=\$)\d+/;
const text = 'This cost $400';
text.match(regex) === ['400']
Всё дело в новой lookbehind-группе, близнеце lookahead-групп:
Look ahead: (?=abc)
Look behind: (?<=abc)
Look ahead negative: (?!abc)
Look behind negative: (?<!abc)
К сожалению, сегодня нельзя транспилировать новый lookbehind-синтаксис под старые браузеры, так что вполне возможно, какое-то время вы сможете использовать его только в Node.
Именованные группы захвата
Теперь регулярные выражения могут выбирать подвыборки и использовать для простого парсинга. До недавнего времени мы могли ссылаться на такие фрагменты только по числам, например:
const getNameParts = /(\w+)\s+(\w+)/g;
const name = "Weyland Smithers";
const subMatches = getNameParts.exec(name);
subMatches[1] === 'Weyland'
subMatches[2] === 'Smithers'
А теперь есть синтаксис присвоения имён этим подвыборкам (или группам записи): внутри скобок в начале ставим
?<titlе>
, если хотим присвоить группе имя:const getNameParts = /(?<first>\w+)\s(?<last>\w+)/g;
const name = "Weyland Smithers";
const subMatches = getNameParts.exec(name);
const {first, last} = subMatches.groups
first === 'Weyland'
last === 'Smithers'
К сожалению, сейчас это работает только в Chrome и Node.
Теперь точки могут отмечать новые строки
Нужно только проставлять флаг
/s
, например, /someRegex./s
, /anotherRegex./sg
.ES2019
Array.prototype.flat() и flatMap()
Я был очень рад увидеть это в MDN.
Попросту говоря,
flat()
преобразует многомерный массив в одномерный на заданную максимальную глубину (depth
):const multiDimensional = [
[1, 2, 3],
[4, 5, 6],
[7,[8,9]]
];
multiDimensional.flat(2) === [1, 2, 3, 4, 5, 6, 7, 8, 9]
flatMap
— это map
, за которым идёт flat
с глубиной 1. Это полезно, если нужно мапить функцию, которая возвращает массив, но при этом вам не нужно, чтобы результат представлял собой вложенную структуру данных:const texts = ["Hello,", "today I", "will", "use FlatMap"];
// with a plain map
const mapped = texts.map(text => text.split(' '));
mapped === ['Hello', ['today', 'I'], 'will', ['use', 'FlatMap']];
// with flatmap
const flatMapped = texts.flatMap(text => text.split(' '));
flatMapped === ['Hello', 'today', 'I', 'will', 'use', 'FlatMap'];
Неограниченные перехваты
Теперь вы можете писать выражения try/catch без привязки к киданию ошибок:
try {
// something throws
} catch {
// don't have to do catch(e)
}
Кстати, перехваты, в которых вы не учитываете значение
e
, иногда называют обработкой исключений-покемонов. Потому что вы должны поймать их все!Методы обрезки строковых значений
Незначительно, но приятно:
const padded = ' Hello world ';
padded.trimStart() === 'Hello world ';
padded.trimEnd() === ' Hello world';