Несмотря на то, что в последние семь лет я пишу на 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';
Комментарии (52)
Devgru
31.10.2019 00:36+2> Теперь точки могут отмечать новые строки
> Нужно только проставлять флаг /s
Возможно, вы имели в виду:
Теперь точка в регулярных выражениях может захватывать переводы строк, эмодзи и некоторые другие символы, которые раньше не захватывала, если включить флаг /s.
Devgru
31.10.2019 00:38+2> Кстати, перехваты, в которых вы не учитываете значение e, иногда называют обработкой исключений-покемонов. Потому что вы должны поймать их все!
Вот это тоже странно.
Шутка про исключения-покемоны возникла в джаве, где можно отлавливать исключения по типу. В ES мы всегда «ловим их все», если пишем catch.
Torkins
31.10.2019 10:22Автору спасибо за обзор последних ES, но вот тема ES2019 не раскрыта.
Там одни chaining (это про '.?'. Кому перечислить денег за внедрение этой фишки?..) и pipelines чего стоят!mayorovp
31.10.2019 13:23Что вы имеете в виду говоря "pipelines"? Если вы про оператор
|>
— то он до сих пор в stage 1, и в ES2019 не сможет попасть никаким чудом.RifleR
31.10.2019 20:34Строго говоря, сейчас ничего новое вообще не может попасть в ES2019, так как ES2019 уже финализирован и утвержден. Но если перейти по ссылке, то там написано, что chaining и pipelines не входят в ES2019, странно, что предыдущий комментатор этого не заметил.
funny_falcon
31.10.2019 10:41Хмм… `trimStart` уже есть… Осталось дождаться `leftPad`.
Aquahawk
31.10.2019 10:45Вы не поверите github.com/tc39/proposal-string-pad-start-end. В том что оно принято можно удостовериться тут github.com/tc39/proposals/blob/master/finished-proposals.md
gatoazul
31.10.2019 13:07Кому нужны двоичные литералы в языке, который не поддерживает целых чисел?
mayorovp
31.10.2019 13:25А в чём заключается противоречие?
gatoazul
31.10.2019 14:02Обычно такие литералы используются для битовых масок. Но для JS с его 64-битовыми float непонятно, что логические операции с этими масками в принципе делают.
yeswell
31.10.2019 14:32Если числа целые (то есть если у них нет дробной части), то делают именно то, что вы ожидаете от битовых операций над беззнаковыми числами.
tbl
31.10.2019 14:49Хм, обнаружил, что применение битовых операций к числу в хроме приводит их к целочисленному значению. А значит можно делить целочисленно: a/b | 0, не используя Math.floor, который округляет отрицательные числа не так, как хотелось бы нематематикам.
Aquahawk
31.10.2019 14:52а ещё num >>> 0 и получем uint32. И полноценная арифметика на интах работает
mayorovp
31.10.2019 18:56Это не только в хроме, это в стандарте так. Трюк с
|0
даже вошёл в asm.js как указание на целое число. Но это всё следствия динамической типизации, операция|
не для этого придумывалась.
Free_ze
31.10.2019 14:51+2В JS-движках (по крайней мере в V8) есть оптимизации, которые вычленяют короткое целочисленное значение из числовых операндов при возможности и оперируют уже с ними. Рантаймовая магия, о которой юзерам языка думать не нужно.
igormich88
У меня в Chrome 78.0.3904.70
Ну и если предположить, что , то чему будет равно 0/0?qbz
Ну, так делить на ноль вроде как нельзя, нет? Или что вас удивляет?
igormich88
В JS можно, как раз NaN и получается.
qbz
Ну, так NaN это нот-э-намбэр, то есть не валидная мат-операция, к примеру. Попробуйте пятерку разделить на "привет", то же самое будет.
igormich88
Согласен, и Number.isNaN вернёт true для 0/0 (и для 6/«привет»), а в статье написано что вернёт false.
qbz
А, ясно. Я не читал примеры, но там, судя по всему, ошибка.
ScreamPassion
Первое — вполне валидная строка (и я тоже не понимаю зачем она там для примера) и она действительно вернет false.
Второе — мат.операция, результат которой NaN
Starche
В статье исправили, раньше было с ошибкой — просто 0/0 без кавычек.
mosby
Обращу внимание что NaN выйдет только при 0/0.
Если делить любое другое число на 0 то получим Infinity.
И кстати
tbl
Ещё в копилку:
marfenkov
Тут дело просто в порядке операций
vanxant
… что абсолютно соответствует принятым в матане соглашениям. 0/0 — неопределённость, 1/0 — бесконечность, и они друг другу не равны. Вот если вы попробуете сделать 2/0 — 1/0, тогда снова будет NaN
lorc
Интересный у вас матан. По определению «a» делится на «b», если существует такое «с», что a = bc.
Рассмотрим 1 / 0. Тогда нам нужно такое «с», что 1 = 0 * c. Очевидно, его не существует. Результат умножения бесконечности на ноль не определен, потому что бесконечность — не число. Если пользоваться теорией пределов, то можно получить любое значение. Да и даже чисто умозрительно: если 1 / 0 = inf и 2 / 0 = inf, то 1 / 0 = 2 / 0 = inf. Множим на ноль и получаем что inf * 0 = 1 = 2. А если множить на ноль по другому, то получим что inf * 0 = 0 = 0.
Но тогда непонятно почему 1 / 0 = inf, если inf * 0 = 0.
Короче, с бесконечностями можно играться только в пределах.
А вот ноль на ноль делиться. Действительно: 0 = 0 * с. «c» тут может быть любым. Таким образом, хоть 0 на 0 и делиться (согласно определению), но результат этого деления не определен.
С точки зрения математики, бесконечность числом не является. А вот в IEEE 754 — является, судя по всему. Во всяком случае там +inf, -inf и NaN — разные сущности.
vanxant
Нормальный у меня матан, советский, качественный. У авторов IEEE 754 был такой же. Бесконечность — два особых числа, о их сути можно спорить (зависит от того, какие аксиомы мы выбрали в теории множеств и арифметике, которые обе заведомо неполны по Геделю). Но в любом случае для бесконечностей определены все арифметические операции с обычными числами кроме 0, а также часть операций с 0 и другой бесконечностью.
А вот неопределённость — это не число, not-a-number. Абсолютно любая операция числа с не-числом даёт не-число.
lorc
Извините, но нет.
1 + inf = inf
2 + inf = inf
Такое возможно только если inf — это ноль. Но это не ноль по определению.
В принципе, из этого уже видно что добавление inf в множество чисел ломает групповую операцию. Т.е. с точки зрения теории групп, бесконечность никак не может входить в множество чисел. Но окей, тут действительно можно спорить о выборе аксиоматики для чисел. Давате зайдем с другой стороны.
То что вы описываете, называется «расширенной числовой прямой»: к обычным числам мы добавляем две неведомых зверюшки-бесконечности. И даже там деление на ноль не приводит к бесконечности:
Короче, проблема в том, что 1 / f не сходится к +бесконечности или -бесконечности. Оно сходится и туда и туда одновременно. Собственно, поэтому в анализе и различают «просто» бесконечность, положительную и отрицательную бесконечности.
В том время как в IEEE 754 деление положительного числа на ноль дает положительную бесконечность, а отрицательного — отрицательную. Хотя, согласно матану, положительную бесконечность можно получить если брать односторонний предел 1 / f когда f стремиться к нулю справа. А если брать обычный предел, то как было написано выше получится «просто» бесконечность. Которой нет ни на расширенной числовой прямой, ни в IEEE 754.
Короче, еще раз: в обычной арифметике на ноль делить можно только сам ноль. На расширенной числовой прямой — тоже. Имеет смысл рассматривать пределы вида 1 / x, когда x->0. Но делить на ноль — все равно нельзя. Зато можно делить на бесконечность, да. И
vanxant
Вы зачем-то пытаетесь приплести матан, хотя здесь просто арифметика. В ней нет пределов, разрывов производных и прочей зауми, там только числа и 4 операции.
И да, откуда у вас появилось требование единственности нейтрального элемента операции сложения? То, что вы знаете про моноиды, ещё не значит, что всё вокруг обязано быть моноидами.
mayorovp
А если нет никаких пределов и разрывов — то и бесконечностей быть не должно, на нормальной числовой прямой их нет.
vanxant
Почему нет? Вы лично проверяли?:) В некоторых аксиоматиках нет, в других есть. Первые используются в школьных учебниках, вторые — на практике.
lorc
Ну так просто в арифметике нет вообще никаких бесконечностей. Им там просто неоткуда взяться. И результат деления на 0 либо не существует (если делим не-ноль), либо — не определен (если делим ноль).
Вообще -то я не говорил про требование единственности нейтрального элемента. Тем более, что бесконечность им не является: а + (-а) = 0, а не inf.
Я сказал что добавление бесконечности ломает групповую операцию. Потому что inf + -inf не определено, например.
Вообще, если вы не хотите приплетать матан, то тогда вообще непонятно зачем добавлять бесконечности. Пользы то от этого никакой не будет. Ну да, можно будет сказать a / 0 = inf. А дальше что? Профит в чем? Проблемы вижу: придется в каждую теорему теории чисел добавлять фразу наподобие «кроме случаев, когда n = inf». Попробуйте докажите основную теорему арифметики, если у вас могут быть бесконечности…
vanxant
Вы ищете теоретический смысл, а он тут сугубо практический.
Обычная машинная арифметика — это арифметика остатков по модулю 2N. Она не очень-то подходит для обычных вычислений из-за переполнений. Скажем, складывая на 8-битном процессоре 100+100, вы получите результат -56. Мало того, что он некорректен, он выглядит нормально. Если такое произойдёт в процессе вычисления какой-то длинной формулы, вы никак не сможете понять, что результат расчёта неверен. И дальше эта ошибка поползёт по всей системе.
Известны тысячи багов типа «дупликации» игровых денег, предметов и т.п. как раз из-за переполнения. Но абсолютно то же самое происходит и в бухгалтерском софте, банковском, системах управления станками и самолётами и т.д. — там, где подобные штуки могут приводить — и приводят — к очень тяжёлым последствиям.
В IEEE 754 переполнения нет именно благодаря бесконечностям. Причем, если где-то всплыла бесконечность, вы от неё уже не избавитесь и хотя бы сразу заметите. Это не идеальный способ, но вычисления в числах с произвольной длиной до сих доступны очень не везде.
lorc
Ну как бы да. Математика — теоретическая наука.
Напомню, все началось с того, что вы заявили что в матане 1 / 0 — это бесконечность. Я всего лишь хотел указать на вашу ошибку и предостеречь других.
Зато она быстрая. А где это действительно важно — используется арифметика с насыщением. Некоторые DSP даже аппаратно ее реализуют.
Ну как бы есть библиотеки, которые реализуют saturating arithmetic, есть поддержка проверки переполнения со стороны компиляторов. Надо просто знать инструмент, которым пользуешься.
Кстати, не знаю за станки и самолеты, но финансы в принципе не используют арифметику с плавающей точкой. Только fixed point (или вообще integer, с расчетами в младших единицах валюты).
Это все безусловно хорошо. Но мы то говорили про деление на ноль?
Было бы правильно сказать что в IEEE 754 приняты соглашения, которые приводят к тому, что деление ненулевого числа на 0 приводит к плюс или минус бесконечности, в зависимости от знака числа и от знака нуля. А матан тут вообще не при чем.
И кстати, два нуля — тоже нехилое отступление от теоретической арифметики.
vanxant
Прикладные математики мягко скажем удивлены:)
да, те, которые я видел изнутри, используют целые сотые доли копеек/центов (это позволяет записывать числа типа 12.34%), а десятичная точка выводится только в интерфейсе. И тоже не от хорошей жизни.
Это всё же лучше, чем INT_MIN (который -128 в 8-битных целых), который иногда работает как -0, а иногда как нормальное число, и для которого -(-128)=-128
mayorovp
Когда это INT_MIN работает как -0?
vanxant
При умножении на нечётные положительные или чётные отрицательные числа, например.
mosby
0/0 действительно неопределенность, все логично.
А вот дальше я боюсь вы ошибаетесь.
Что-то я вижу что любое другое число деля на 0 выходит Infinity
vanxant
Да, но если вы возьмёте именно то что я написал (2/0 — 1/0), то будет NaN. Тут вопрос в вычитании, а не в делении. Вычитать бесконечности нельзя.
mosby
все. вы имели в виду вычитание бесконечности от бесконечности.
не правильно понял ваш коммент