Перейти к основному содержимому

Комбинаторы Promise

· 4 мин. чтения
Маттиас Биненс ([@mathias](https://twitter.com/mathias))

С момента появления промисов в ES2015 JavaScript поддерживал ровно два комбинатора promise: статические методы Promise.all и Promise.race.

Два новых предложения в настоящее время проходят процесс стандартизации: Promise.allSettled и Promise.any. С этими дополнениями в JavaScript будут доступны четыре комбинатора promise, каждый из которых позволяет решать свои задачи.

`Array.prototype.flat` и `Array.prototype.flatMap`

· 2 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Array.prototype.flat

Массив в этом примере имеет несколько уровней вложенности: он содержит массив, который, в свою очередь, содержит другой массив.

const array = [1, [2, [3]]];
// ^^^^^^^^^^^^^ внешний массив
// ^^^^^^^^ вложенный массив
// ^^^ самый глубокий массив

Array#flat возвращает одномерную версию многомерного массива.

array.flat();
// → [1, 2, [3]]

// …эквивалентно:
array.flat(1);
// → [1, 2, [3]]

Глубина по умолчанию равна 1, но можно указать любое число для рекурсивного свёртывания до этой глубины. Чтобы продолжать свёртывание рекурсивно, пока в результате не останется вложенных массивов, нужно передать Infinity.

// Рекурсивное свёртывание до полного удаления вложенных массивов:
array.flat(Infinity);
// → [1, 2, 3]

Почему этот метод называется Array.prototype.flat, а не Array.prototype.flatten? Прочтите наш разбор #SmooshGate, чтобы узнать!

Array.prototype.flatMap

Вот ещё один пример. У нас есть функция duplicate, которая принимает значение и возвращает массив, содержащий это значение дважды. Если применить duplicate к каждому значению массива, получится вложенный массив.

const duplicate = (x) => [x, x];

[2, 3, 4].map(duplicate);
// → [[2, 2], [3, 3], [4, 4]]

Затем можно вызвать flat для результата, чтобы свести массив:

[2, 3, 4].map(duplicate).flat(); // 🐌
// → [2, 2, 3, 3, 4, 4]

Поскольку этот шаблон очень распространён в функциональном программировании, для него теперь существует специальный метод flatMap.

[2, 3, 4].flatMap(duplicate); // 🚀
// → [2, 2, 3, 3, 4, 4]

flatMap немного эффективнее, чем выполнение map, за которым следует flat по отдельности.

Интересуетесь вариантами использования flatMap? Ознакомьтесь с объяснением Акселя Раушмайера.

Поддержка Array#{flat,flatMap}

Разделители цифр

· 2 мин. чтения
Маттиас Байненс ([@mathias](https://twitter.com/mathias))

Большие числовые литералы трудно быстро воспринимать человеческим глазом, особенно когда много повторяющихся цифр:

1000000000000
1019436871.42

Для улучшения читаемости новая языковая возможность JavaScript позволяет использовать подчеркивания в качестве разделителей в числовых литералах. Таким образом, приведенный выше пример можно переписать, группируя цифры по тысячам, например:

`String.prototype.matchAll`

· 3 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias))

Часто нужно повторно применять одно и то же регулярное выражение к строке, чтобы получить все совпадения. В некоторой степени это уже возможно с помощью метода String#match.

В этом примере мы найдем все слова, состоящие только из шестнадцатеричных символов, а затем выведем каждое совпадение:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.match(regex)) {
console.log(match);
}

// Результат:
//
// 'DEADBEEF'
// 'CAFE'

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

Уже сейчас можно достичь этого, написав собственный цикл и ведя учет объектов совпадений вручную, но это немного неудобно и не очень удобно:

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
let match;
while (match = regex.exec(string)) {
console.log(match);
}

// Результат:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

Новый API String#matchAll делает это проще, чем когда-либо: теперь вы можете написать простой цикл for-of, чтобы получить все объекты совпадений.

const string = 'Magic hex numbers: DEADBEEF CAFE';
const regex = /\b\p{ASCII_Hex_Digit}+\b/gu;
for (const match of string.matchAll(regex)) {
console.log(match);
}

// Результат:
//
// [ 'DEADBEEF', index: 19, input: 'Magic hex numbers: DEADBEEF CAFE' ]
// [ 'CAFE', index: 28, input: 'Magic hex numbers: DEADBEEF CAFE' ]

String#matchAll особенно полезен для регулярных выражений с группами захвата. Он предоставляет полную информацию для каждого отдельного совпадения, включая группы захвата.

const string = 'Favorite GitHub repos: tc39/ecma262 v8/v8.dev';
const regex = /\b(?<owner>[a-z0-9]+)\/(?<repo>[a-z0-9\.]+)\b/g;
for (const match of string.matchAll(regex)) {
console.log(`${match[0]} на ${match.index} в '${match.input}'`);
console.log(`→ владелец: ${match.groups.owner}`);
console.log(`→ репо: ${match.groups.repo}`);
}

`Intl.ListFormat`

· 3 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias)) и Фрэнк Юнг-Фонг Танг

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

Эта фотография включает Аду, Эдит и Грейс.

Текстовая игра может иметь другой вид списка:

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

Так как у каждого языка есть свои собственные правила форматирования списков и слова, реализация локализованного форматирования списка является нетривиальной задачей. Это требует не только списка всех слов (например, «и» или «или» в приведенных выше примерах) для каждого поддерживаемого языка — к тому же нужно описать точные правила форматирования для всех этих языков! Unicode CLDR предоставляет эти данные, но чтобы использовать их в JavaScript, их нужно интегрировать и включить в библиотеку. Это, к сожалению, увеличивает размер пакета для таких библиотек, что негативно сказывается на времени загрузки, стоимости обработки/компиляции и потреблении памяти.

Экспорт пространства имен модулей

· 1 мин. чтения
Маттиас Байненс ([@mathias](https://twitter.com/mathias))

В модулях JavaScript уже можно было использовать следующий синтаксис:

import * as utils from './utils.mjs';

Однако симметричный синтаксис export отсутствовал… до сегодняшнего дня:

export * as utils from './utils.mjs';

Это эквивалентно следующему:

import * as utils from './utils.mjs';
export { utils };

Публичные и приватные поля классов

· 4 мин. чтения
Матиас Биненс ([@mathias](https://twitter.com/mathias))

Несколько предложений расширяют существующий синтаксис классов JavaScript новыми функциями. В этой статье объясняется новый синтаксис публичных полей классов в V8 v7.2 и Chrome 72, а также предстоящий синтаксис приватных полей классов.

Вот пример кода, который создает экземпляр класса с именем IncreasingCounter:

const counter = new IncreasingCounter();
counter.value;
// выводит 'Получение текущего значения!'
// → 0
counter.increment();
counter.value;
// выводит 'Получение текущего значения!'
// → 1

Обратите внимание, что доступ к value выполняет некоторый код (например, записывает сообщение) перед возвратом результата. Теперь задайте себе вопрос, как вы бы реализовали этот класс на JavaScript? 🤔

Синтаксис классов ES2015

Вот как можно было бы реализовать IncreasingCounter с использованием синтаксиса классов ES2015:

class IncreasingCounter {
constructor() {
this._count = 0;
}
get value() {
console.log('Получение текущего значения!');
return this._count;
}
increment() {
this._count++;
}
}

Класс устанавливает геттер value и метод increment на прототипе. Более интересно то, что класс имеет конструктор, который создает свойство экземпляра _count и задает для него значение по умолчанию 0. В настоящее время мы часто используем префикс подчеркивания, чтобы указать, что _count не должен напрямую использоваться потребителями класса, но это всего лишь соглашение; на самом деле это не приватное свойство со специальной семантикой, обеспечиваемой языком.

`Intl.RelativeTimeFormat`

· 4 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

Современные веб-приложения часто используют выражения вроде «вчера», «42 секунды назад» или «через 3 месяца» вместо полных дат и временных меток. Такие значения относительного времени стали настолько распространёнными, что несколько популярных библиотек реализовали утилитарные функции для их локализованного форматирования. (Примеры включают Moment.js, Globalize и date-fns.)

Хорошо сформированный `JSON.stringify`

· 1 мин. чтения
Mathias Bynens ([@mathias](https://twitter.com/mathias))

JSON.stringify ранее был определен так, что возвращал некорректные строки Unicode, если входные данные содержали одиночные суррогаты:

JSON.stringify('\uD800');
// → '"�"'

Предложение «хорошо сформированного JSON.stringify» изменяет JSON.stringify, чтобы он выводил экранированные последовательности для одиночных суррогатов, делая его вывод валидным Unicode (и представимым в UTF-8):

Модули JavaScript

· 18 мин. чтения
Эдди Османи ([@addyosmani](https://twitter.com/addyosmani)) и Матиас Биннс ([@mathias](https://twitter.com/mathias))

Модули JavaScript теперь поддерживаются во всех основных браузерах!

Эта статья объясняет, как использовать JS-модули, как их ответственно развертывать, и как команда Chrome работает над их дальнейшим улучшением в будущем.

Что такое JS-модули?

JS-модули (также известные как «ES-модули» или «модули ECMAScript») — это большая новая функция или, скорее, набор новых функций. Возможно, вы ранее использовали пользовательскую систему модулей JavaScript. Возможно, вы использовали CommonJS, как в Node.js, или, возможно, AMD, или что-то другое. У всех этих систем модулей есть одна общая черта: они позволяют импортировать и экспортировать данные.