Главная Категории Контакты Поиск

JavaScript ES2021 / ES12 новые возможности

Что нового в ECMAScript 2021.

JavaScript ·17.02.2022·читать 13 мин 🤓·Автор: Alex Myzgin

Вот новые функции, добавленные в ES12.

Приватные методы

Приватные методы могут быть доступны только внутри класса, в котором они определены. Имена приватных методов начинаются с символа #.

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 могут создать больше проблем, чем решить. Они охватывают некоторые конкретные сценарии, и ими нельзя злоупотреблять.

Надеюсь, эта статья была полезной для тебя. Большое спасибо за прочтение!

Website, name & logo
Copyright © 2022. Alex Myzgin