![](https://habrastorage.org/webt/8n/s0/1-/8ns01-8nj9jykn7rja5vhi4jfli.jpeg)
Я подробно расскажу о 5 самых интересных возможностях ECMAScript 2021. Это позволит вам приступить к их использованию в своих проектах, что упростит вам работу и улучшит ваш код. Эта статья может оказаться полезной как начинающим, так и опытным разработчикам.
1. Разделители разрядов чисел
Разделители разрядов чисел позволяют записывать числовые литералы с использованием символов подчёркивания, что улучшает читабельность чисел. Система автоматически удаляет эти символы при подготовке кода к запуску. В следующем коде демонстрируется пример использования разделителей.
// Десятичное число, разряды которого разделены символом подчёркивания.
let n1 = 1_000_000_000;
console.log(n1); // Выведет: 1000000000
// Десятичное число, разряды которого разделены символом подчёркивания.
let n2 = 1_000_000_000.150_200
console.log(n2); // Выведет: 1000000000.1502
// Шестнадцатеричное число, байты которого разделены символом подчёркивания.
let n3 = 0x95_65_98_FA_A9
console.log(n3); // Выведет: 641654651561
// BigInt-число, разряды которого разделены символом подчёркивания.
let n4 = 155_326_458_156_248_168_514n
console.log(n4); // This will print: 155326458156248168514n
2. Метод String.prototype.replaceAll()
Новый метод строковых объектов
replaceAll()
позволяет заменить на что либо все вхождения подстроки в строке. При использовании строкового метода replace()
осуществляется лишь замена первого вхождения искомого значения. А метод replaceAll()
позволяет заменить все вхождения такого значения. Вот пример:// Объявляем константу, хранящую некое строковое значение.
const orgStr = 'JavaScript, often abbreviated as JS, is a programming language that conforms to the ECMAScript specification. JavaScript is high-level, often just-in-time compiled and multi-paradigm.';
// Для того чтобы заменить в исходной строке лишь первое вхождение искомой подстроки и сформировать новую строку - воспользуемся методом replace().
let newStr = orgStr.replace('JavaScript', 'TypeScript');
console.log(newStr);
// Для замены всех вхождений искомой строки воспользуемся методом replaceAll().
let newStr2 = orgStr.replaceAll('JavaScript', 'TypeScript');
console.log(newStr2);
3. Метод Promise.any() и объект AggregateError
Метод
Promise.any()
можно назвать полной противоположностью метода Promise.all()
. Promise.any()
, при передаче ему итерируемого объекта с промисами, возвращает промис со значением первого успешно выполненного промиса. Если ни один из промисов не разрешён — метод возвращает сведения об ошибках, в частности — объект AggregateError
. А Promise.all()
ждёт разрешения всех промисов. Есть и другие методы объекта Promise
. Например — Promise.allSettled()
, который ожидает успешного завершения или отклонения всех переданных ему промисов.Рассмотрим пример:
// Создаём объект Promise.
const promise1 = new Promise((resolve, reject) => {
// Разрешить промис через 2 секунды.
setTimeout(() => resolve("The first promise has been resolved."), 2000);
});
// Создаём объект Promise.
const promise2 = new Promise((resolve, reject) => {
// Разрешить промис через 1 секунду.
setTimeout(() => resolve("The second promise has been resolved."), 1000);
});
// Создаём объект Promise.
const promise3 = new Promise((resolve, reject) => {
// Разрешить промис через 3 секунды.
setTimeout(() => resolve("The third promise has been resolved."), 3000);
});
(async function () {
const data = await Promise.any([promise1, promise2, promise3]);
// Выводим данные, возвращённые из первого разрешённого промиса.
console.log(data);
// Предыдущая команда выведет следующее: The second promise has been resolved.
})()
Если все промисы были отклонены — будет возвращён объект
AggregateError
, содержащий сведения об ошибках. Обратитесь к следующему примеру для того чтобы разобраться с тем, как обрабатывать подобные ошибки:// Создаём объект Promise.
const promise1 = new Promise((resolve, reject) => {
// Отклонить промис через 1 секунду.
setTimeout(() => reject("The first promise has been rejected."), 1000);
});
// Создаём объект Promise.
const promise2 = new Promise((resolve, reject) => {
// Отклонить промис через 500 миллисекунд.
setTimeout(() => reject("The second promise has been rejected."), 500);
});
// Попытаемся выполнить промисы.
(async function () {
try {
const data = await Promise.any([promise1, promise2]);
console.log(data);
} catch (error) {
// Если все промисы отклонены, мы попадём в этот блок try-catch, где
// можно будет обработать ошибки, с которыми завершилась работа промисов.
console.log("Error: ", error);
}
})();
4. Логические операторы присваивания
В ECMAScript 2021 появились три новых оператора, которые представляют собой комбинацию оператора присваивания и логических операторов.
- Логический оператор присваивания ИЛИ (OR):
||=
. - Логический оператор присваивания И (AND):
&&=
. - Логический оператор присваивания с проверкой значений на
null
иundefined
:??=
.
▍4.1. Оператор ||=
Оператор
||=
принимает два операнда и назначает правый операнд левому только в том случае, если левый операнд является ложным. Вот пример:// Оператор ||= проверит, не является ли ложным (0) значение songsCount.
// Если это так, правый операнд будет записан в переменную, находящуюся в левой части выражения.
let myPlaylist = {songsCount: 0, songs:[]};
myPlaylist.songsCount ||= 100;
console.log(myPlaylist); // Выведет: {songsCount: 100, songs: Array(0)}
Этот оператор работает по сокращённой схеме вычислений. Он эквивалентен следующему выражению, в котором используется логический оператор ИЛИ:
a || (a = b)
▍4.2. Оператор &&=
Оператор
&&=
присваивает правый операнд левому только в том случае, если значение, находящееся слева, является истинным. Рассмотрим пример:// Оператор &&= проверит, является ли значение filesCount истинным.
// Если это так - правый операнд будет присвоен левому.
let myFiles = {filesCount: 100, files:[]};
myFiles.filesCount &&= 5;
console.log(myFiles); // Выведет: {filesCount: 5, files: Array(0)}
Оператор
&&=
тоже работает по сокращённой схеме вычислений. Этот оператор эквивалентен следующему выражению, в котором используется логический оператор И:a && (a = b)
▍4.3. Оператор ??=
Оператор
??=
присвоит правый операнд левому только в том случае, если левый оператор представлен значением null
или undefined
. Например:// Оператор ??= проверит, равняется ли lastname null или undefined.
// Если это так - правый операнд будет присвоен левому.
let userDetails = {firstname: 'Katina', age: 24}
userDetails.lastname ??= 'Dawson';
console.log(userDetails); // Выведет: {firstname: 'Katina', age: 24, lastname: 'Dawson'}
5. Приватные методы экземпляров классов и методы доступа к свойствам
Свойства и методы экземпляров классов в JavaScript, по умолчанию, общедоступны. Но теперь можно создавать приватные методы и свойства с использованием префикса
#
. Доступны они только изнутри экземпляра класса. Вот как пользоваться приватными методами:// Создадим класс User.
class User {
constructor() {}
// Приватный метод можно создать, поместив '#' перед
// именем метода.
#generateAPIKey() {
return "d8cf946093107898cb64963ab34be6b7e22662179a8ea48ca5603f8216748767";
}
getAPIKey() {
// Обратиться к приватному методу можно, поставив '#' перед
// именем метода.
return this.#generateAPIKey();
}
}
const user = new User();
const userAPIKey = user.getAPIKey();
console.log(userAPIKey); // Выведет: d8cf946093107898cb64963ab34be6b7e22662179a8ea48ca5603f8216748767
Приватные методы доступа к свойствам объектов — это приватные геттеры и сеттеры. Геттер позволяет получить значение свойства, а сеттер позволяет назначить свойству значение.
Объявить приватный геттер можно так:
get #newAccountPassword() {}
Так можно объявить приватный сеттер:
set #generateAccountPassword(newPassword) {}
Вот как ими пользоваться:
// Создадим класс Str.
class Str {
// Приватные свойства можно создавать, помещая '#'
// перед их именами.
#uniqueStr;
constructor() {}
// Приватный сеттер можно создать, поместив '#' перед
// его именем.
set #generateUniqueStringByCustomLength(length = 24) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let randomStr = "";
for (let i = 0; i < length; i++) {
const randomNum = Math.floor(Math.random() * characters.length);
randomStr += characters[randomNum];
}
this.#uniqueStr = randomStr;
}
// Общедоступный сеттер
set setRandomString(length) {
this.#generateUniqueStringByCustomLength = length;
}
// Приватный геттер можно создать, поместив '#' перед
// его именем.
get #fetchUniqueString() {
return this.#uniqueStr;
}
// Общедоступный геттер
get getRandomString() {
return this.#fetchUniqueString;
}
}
const str = new Str();
// Вызываем общедоступный сеттер, который обратится к приватному сеттеру,
// находящемуся в том же объекте, что и он.
str.setRandomString = 20;
// Вызываем общедоступный геттер, который обратится к приватному геттеру,
// находящемуся в том же объекте, что и он.
const uniqueStr = str.getRandomString;
console.log(uniqueStr); // Выводит случайную строку каждый раз, когда геттер вызывают после вызова сеттера
Итоги
Теперь вы знаете, как пользоваться некоторыми новыми возможностями JavaScript ES12 (ECMAScript 2021). А значит — пришло время применить эти знания на практике.
Пользуетесь ли вы возможностями JavaScript ES12 в своих проектах?
![](https://habrastorage.org/webt/ou/g5/kh/oug5kh6sjydt9llengsiebnp40w.png)
Комментарии (25)
strannik_k
18.11.2021 22:45+8Мда, скоро код на JS будет выглядеть как обфусцированный)
leon0399
19.11.2021 03:28+3Особенно смущает решение использовать # в качестве маркера приватных методов. Лично я пишу не только на JS и для меня это все ещё остаётся маркером комментариев
Format-X22
19.11.2021 04:44Подсветка синтаксиса решит не всю, но большую часть вашей боли. А так - было бы на клавиатуре побольше разных других знаков - возможно был бы он, но, по сути, это последний такой не используемый. Можно конечно ещё дописать private, это валидно, хоть и многословно, но может сломать TypeScript - приватные методы оттуда не эквивалентны тем что добавили в JS т.к. у добавленных приватность на уровне объекта самого, а не доступа до поля, разное поведение. Ну и в целом, возможно, плохая идея многословности добавлять именно в сам JS. Но всё это так, сугубо мои личные мысли.
Immortal_pony
19.11.2021 07:22+4Но на клавиатуре уже есть символ, который помогает пометить приватные методы в языках, которые их не поддерживают и этот символ "_". Почему бы просто не использовать его?
Также можно было бы добавить ключевое слово "private" и вообще не использовать никакие странные символы.movl
19.11.2021 11:58Просто приватные поля, это прям приватные, единственное место где к ним можно обращаться, это в рамках класса. Символ нужен чтобы интерпретатор смог понять, что идет обращение к приватному полю. При этом обращение через
obj['#private']
остается валидным, и не является обращением к приватному полю, что позволяет сохранить обратную совместимость.
Format-X22
19.11.2021 14:27+3Если использовать _, то тогда мы сломаем с ходу огромное количество кода т.к. это валидное имя сейчас и в легаси много кто вызывает приватные методы, помеченные так, хотя как бы не правильно, но раз можно физически - то делают если очень надо. Будет не очень весело если апдейт языка половину интернета сломает, думаю вы со мной согласитесь.
Akuma
22.11.2021 12:38Да и не в легаси :)
Как минимум, для тестов иногда полезно. Всегда проще напрямую вызвать в тесте obj._private() с @ts-ignore чем писать какие-то обертки
Pavel1114
19.11.2021 07:17+1Не густо. В основном косметика, кроме промисов и приватных методов. В сравнении с es6 — минорное обновление. Но так и должно быть — js довольно зрелый язык.
artem_ibragimov
19.11.2021 09:23Непонятно, чем отличается
Promise.any
отPromise.race
, лучше бы сравнили с ним, чем сPromise.all
dopusteam
19.11.2021 09:27+2set #generateUniqueStringByCustomLength(length = 24) {
this.#generateUniqueStringByCustomLength = length;
Выглядит ужасно, когда вместо метода делают сеттер, который тут совсем ни к чему.
pavelsc
24.11.2021 02:04-1Меня ещё сеттеры начинающиеся с set и геттеры с get покоробили. Они в принципе предполагают отсутствие любого глагола в имени. Надо на собеседованиях А4 страничку подобного шлака подавать и маркер, точно полезнее всякого литкода будет. А лучше ноут с кодом и потом мержреквест глянуть через полчаса )
Vladivo
19.11.2021 09:38-3Ещё чуть-чуть, кажется, и нативные типы с дженериками подвезут. Во заживём!
MightyRavendark
19.11.2021 14:05+1Добавили относительно бесполезные разделители разрядов чисел, при этом на реально нужные числа фиксированных размерностей даже предложения вроде нет. Если JS начинает позиционировать себя как язык для полноценных приложений (PWA), то часто требуется, например, unsigned математика, или деление с отбрасыванием остатка . В нормальных языках это решается путем объявления числа как unsigned и присваивание результата деления в int-переменную. В JS-же приходится писать костыли. При этом, т.к. все числа в js, по сути float64 - то на сложных вычислениях вообще получается ужас, как по костылям, так и по производительности. В некоторых частях кода, я наблюдал, как программисты делали примерно такой код:
let uint8 = new Uint8Array([originalNumberValue]);
Т.е. для создания и работы с одним uint8 числом, программистам приходится создавать TypedArray! Ну, либо, мучаться с битовыми масками и Math-функциями.
JimDi
22.11.2021 05:42что странно - у меня в примере с запуском промисов первый срабатывает сначала. ЧЯДНТ?
Louter
22.11.2021 23:05Ещё интересен WeakRef (странная дичь, конешн), но блин
Стандарт ES: вот вам разделители, вот вам приватные свойства и методы, даже промисы апнули!
Двойное сравнение (a < b < c): ну да, ну да, пошли мы нахренFodin
23.11.2021 00:26+1Двойного сравнения не будет в таком виде, т.к. такой синтаксис сейчас вполне валиден, просто обозначает не двойное сравнение.
Bigata
Интересно, что быстрее replace() с регуляркой или replaceAll()
vanxant
replaceAll тоже может принимать регулярку, тогда это просто /g
Вот если искомая строка - строка, то replaceAll должно быть чуть быстрее, как минимум на этапе компиляции.