Вот новые функции, добавленные в ES12.
- Приватные методы;
- Метод String replaceAll;
- WeakRef;
- Финализаторы;
- Promise.any() и AggregateError;
- Оператор логического присваивания;
- Подчеркивание как числовой разделитель;
- Заключение;
Приватные методы
Приватные методы могут быть доступны только внутри класса, в котором они определены. Имена приватных методов начинаются с символа #
.
class Person {
// Приватный метод
#protected() {
console.log("I am Private");
}
// Публичный метод
print() {
this.#protected();
}
}
const personObj = new Person();
personObj.print(); // "I am Private";
personObj.protected(); // TypeError: personObj.protected is not a function
Поскольку protected()
является приватным методом, personObj.protected
возвращает значение undefined
. Попытка использовать undefined
в качестве функции вызывает ошибку TypeError
.
Метод String replaceAll()
В прототип String
добавлена новая функция. До этого добавления невозможно было заменить все экземпляры подстроки без использования регулярного выражения.
String.prototype.replaceAll()
заменяет все вхождения строки другим строковым значением.
В настоящее время в строке JavaScript есть метод replace()
. Его можно использовать для замены подстроки другой строкой.
// using Regex to replace
"1 2 1 2 1 2".replace("2", "0");
// → '1 0 1 2 1 2'
Если входной шаблон для замены является строкой, то метод replace()
заменяет только первое вхождение. Поэтому в коде все вложения, кроме первого, не заменяются.
Ты сможешь сделать полную замену, только если предоставишь шаблон для замены в виде регулярного выражения.
// используя Regex для замены
"1 2 1 2 1 2".replace(new RegExp("2", "g"), "0");
// → '1 0 1 0 1 0'
// другой вариант
"1 2 1 2 1 2".replace(/2/g, "0");
// → '1 0 1 0 1 0'
Благодаря replaceAll
это можно сделать так:
// String.prototype.replaceAll(searchValue, replaceValue)
"1 2 1 2 1 2".replaceAll("2", "0");
// → '1 0 1 0 1 0'
Что произойдет, если аргумент поиска - пустая строка? Он вернет замещенное значение между каждой единицей кода UCS-2 / UTF-16.
"x".replace("", "_");
// → '_x'
"xxx".replace(/(?:)/g, "_");
// → '_x_x_x_'
"xxx".replaceAll("", "_");
// → '_x_x_x_'
WeakRef
В JavaScript есть автоматический процесс сборки мусора. Он может собирать только недоступные объекты. Каждый раз, когда мы делаем присваивание объекту, то создаем сильную ссылку. Это защищает его от сборки мусора.
// x сильная ссылка на объект
const x = { foo: "bar" };
WeakRef означает слабые ссылки (Weak References). Основное использование слабых ссылок - реализация кешей или маппинг с большими объектами. В таких сценариях мы не хотим удерживать большое количество памяти в течение длительного времени, сохраняя этот редко используемый кеш или маппинг. Мы можем разрешить сборку мусора для памяти в ближайшее время, а позже, если она нам снова понадобится, можем сгенерировать новый кеш.
WeakRef
служит совершенно другому варианту использования: для хранения ссылки на объект, который не защитит объект от сборки мусора. Поскольку сборка мусора не является детерминированной, нет реального понимания, когда конкретно объект будет очищен и произойдет ли это вообще. WeakRef использует данное преимущество, предоставляя тебе доступ к объекту, пока он не будет собран.
WeakRef
может принимать объект только в качестве аргумента.
function Foo() {}
// сильная ссылка на экземпляр Foo
const x = new Foo();
// слабая ссылка на экземпляр Foo
const xWeak = new WeakRef(x);
Как вернуть значение? Используй метод deref()
. И имей в виду, что метод вернет сильную ссылку на объект.
function Foo() {}
// сильная ссылка на экземпляр Foo
const x = new Foo();
// слабая ссылка на экземпляр Foo
const xWeak = new WeakRef(x);
// сильная ссылка на экземпляр Foo
const xFromWeak = xWeak.deref();
Почему это полезно? Это позволяет повысить производительность приложения в зависимости от устройства пользователя. Мы можем использовать WeakRefs
для кеширования больших объектов. Это означает, что машины с большим объемом памяти могут увидеть увеличение производительности приложения. Те, у кого ограниченная память, по-прежнему будут работать, не съедая память пользователя.
JavaScript - это язык со сборкой мусора. Ты можешь узнать больше о сборщике мусора JavaScript здесь, на сайте MDN.
Финализаторы
Финализаторы - еще одна функция ES12 в области памяти. Эта функция сообщает нам, когда объект был собран сборщиком мусора. Это делается с помощью обратного вызова (callback) JavaScript.
Однако следует помнить о нескольких вещах:
- не гарантируется, что обратный вызов будет выполнен;
- целевой объект уже очищен и недоступен;
- длительность выполнения обратного вызова, не является детерминированным. Это может быть как одна минута, так и один час.
// построение метода финализатора
const registry = new FinalizationRegistry((value) => {
// код очистки должен быть здесь
console.log(value);
});
registry
является экземпляром FinalizationRegistry
. Функция обратного вызова, переданная в FinalizationRegistry
, срабатывает, когда объект собирается сборщиком мусора.
(function () {
const obj = {};
// подключаем переменную `obj` к финализатору
registry.register(obj, "Deleted");
})();
На 3-й строке мы подключаем объект к registry
. Когда сборщик мусора забирает obj
, второй аргумент метода .register()
передается функции обратного вызова. Итак, согласно логике нашего кода, когда obj
забирается сборщиком мусора, строка "Deleted"
передается функции обратного вызова и печатается в консоль.
Когда ты выполнишь приведенный выше код в консоли Google Chrome, примерно через 1 минуту он напечатает "Deleted"
. Еще один способ принудительно выполнить сборку мусора в Chrome - щелкнуть значок Collect Garbage
. Ты можешь найти его во вкладке Performance
.
Promise.any() и AggregateError
Есть еще одна полезная утилита - новый метод Promise.any
. В качестве аргумента он принимает повторяющиеся промисы. Когда любое из них выполнено, он вызовет обратный вызов (callback) параметра Promise.any()
или вернет ответ. Это зависит от того, используешь ли ты async/await
или нет.
Когда все промисы терпят неудачу - метод выдаст ошибку AggregateError
, которая объединяет все различные ошибки промисов. Почему бы не вернуть простой массив? В основном для совместимости. Это будет экземпляр ошибки, и ты получишь трассировку стека. Хорошо иметь эту информацию на случай, если она понадобится.
Давай посмотрим на несколько примеров.
Использование традиционного синтаксиса обратного вызова:
Promise.any([
Promise.reject("Error 1"),
Promise.reject("Error 2"),
Promise.resolve("success"),
]).then((result) => {
console.log("result:", result);
});
// result: success
Использование синтаксиса async/await
:
(async () => {
const result = await Promise.any([
Promise.reject("Error 1"),
Promise.reject("Error 2"),
Promise.resolve("success"),
]);
console.log(`result: ${result}`);
})();
// result: success
Теперь давай проверим пример сценария ошибки. В этом случае Promise.any()
генерирует исключение AggregateError
. Нам нужно поймать и справиться с этим.
try {
(async function () {
const result = await Promise.any([Promise.reject("Error 1")]);
console.log(`result: ${result}`);
})();
} catch (error) {
console.log(error.errors);
}
В демонстрационных целях в Promise.any()
передается только одно обещание. И этот промис завершается «с ошибкой» (reject). Приведенный выше код регистрирует следующую ошибку в консоли.
И это четвертое дополнение к прототипу Promise
.
Итак, давай подведем итог! В настоящее время в нашем распоряжении имеется следующее:
- [ES2020]
Promise.allSettled
: этот метод возвращает промис, который выполняется, когда все промисы были выполнены или отклонены. Возвращенный объект описывает каждый отдельный результат промиса; - [ES2015]
Promise.all
: этот метод возвращает промис, который выполняется, только если все целевые промисы были выполнены; - [ES2015]
Promise.race
: этот метод возвращает промис, который будет выполнен, как только один из промисов будет отклонен или выполнен.
Оператор логического присваивания
Наконец, эти новые спецификации ES12 поставляются с долгожданными операторами. Новые операторы объединяют оператор логического присваивания с логическими операциями &&
, ||
и ??
.
Оператор логического присваивания &&=
Давай сравним его эквивалент в ES11 с новой спецификацией.
До:
x && (x = y);
Сейчас:
x &&= y;
Пример:
let x = 5;
const y = 10;
x &&= y;
console.log(x);
// 10
Или по-другому:
if (x) {
x = y;
}
Поскольку x
- истинное значение, ему присваивается значение y
, то есть 10.
Как и в случае с &&
, мы можем поступить и с ||
, а также ??
.
Оператор логического присваивания ||=
Сравниваем его эквивалент в ES11 с новой спецификацией.
До:
x || (x = y);
Сейчас:
x ||= y;
Пример:
let x = 5;
const y = 10;
x ||= y;
console.log(x);
// result is 5
Это означает, что операция присваивания происходит, только если x
является ложным значением. В нашем коде x
содержит 5
, которое является истинным значением, и, следовательно, присваивания не происходит. Вот почему наш код печатает 5
в консоли.
Оператор логического присваивания ??=
??
- это нулевой оператор объединения в JavaScript. Давай вспомним, что он делает, поскольку встречается реже.
Оператор объединения с нулевым значением (??) - это логический оператор, который возвращает его правый операнд, когда его левый равен null
или undefined
, в противном случае возвращает его левый операнд. - MDN
Давай сравним его эквивалент в ES11 с новой спецификацией.
До:
x ?? (x = y);
Сейчас:
x ??= y;
Пример:
let x;
const y = 10;
x ??= y;
console.log(x);
// result is 10
Строка 3 в приведенном выше коде эквивалентна:
x = x ?? (x = y);
Если значение x
равно null
или undefined
, правая часть от ??
оценивается и присваивается y
.
Здесь значение x
равно undefined
. Таким образом, выражение в правой части вычисляется и устанавливает x равным 10
.
Подчеркивание как числовой разделитель
Это дополнение может быть менее необычным, но оно помогает улучшить читаемость твоего кода. В настоящее время при хранении длинного числа в переменной var/let/const
оно может быть нечитабельным.
А сейчас давай посмотрим на вот этот пример. Создадим число 1 миллион. Становится очевидным, что длинные числа трудно читать.
const oneMillion = 1000000;
С новым синтаксисом ES12 ты можешь разделить цифры с помощью символа _
.
const oneMillion = 1_000_000;
console.log(oneMillion); // 1000000
Разделитель подчеркивания _
также работает с числами BigInt
.
const trillion = 1000_000_000_000n;
console.log(trillion.toString()); // "1000000000000"
Его также можно использовать для двоичных и шестнадцатеричных литералов:
const max8bits = 0b1111_1111;
console.log(max8bits); // 255
const message = 0xa0_b0_c0;
console.log(message); // 10531008
Это определенно то, что лично я буду использовать довольно часто.
Заключение
Это интересный выпуск, который предоставляет множество небольших полезных функций, таких как логические операторы присваивания
, replaceAll
и числовые разделители
. Ты уже можешь начать использовать всё это.
Также были добавлены расширенные функции WeakRefs
и Finalizers
. Какими бы сложными они ни были, их необходимо хорошо понять перед использованием. В противном случае WeakRefs
и Finalizers
могут создать больше проблем, чем решить. Они охватывают некоторые конкретные сценарии, и ими нельзя злоупотреблять.
Надеюсь, эта статья была полезной для тебя. Большое спасибо за прочтение!