Большинство объектов Intl создают внутренние структуры
данных во время инициализации:
Создание экземпляра может быть значительно дороже, чем повторный вызов его методов. Особенно это заметно:
Неправильное использование Intl часто приводит к скрытым
потерям производительности.
const prices = [1200, 3400, 9900, 15000];
for (const price of prices) {
const formatter = new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB"
});
console.log(formatter.format(price));
}
На каждой итерации:
При небольшом количестве вызовов проблема незаметна, однако в крупных приложениях стоимость таких операций становится существенной.
const prices = [1200, 3400, 9900, 15000];
const formatter = new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB"
});
for (const price of prices) {
console.log(formatter.format(price));
}
Экземпляр создаётся один раз и используется многократно.
Это:
const formatter = new Intl.NumberFormat("en-US");
rows.forEach(row => {
console.log(formatter.format(row.amount));
});
Без переиспользования на каждую строку создавался бы новый formatter.
При таблицах в тысячи строк разница становится заметной.
const dateFormatter = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "long"
});
events.forEach(event => {
console.log(dateFormatter.format(event.date));
});
Intl.DateTimeFormat считается одним из самых тяжёлых
объектов внутри Intl.
function Price({ value }) {
const formatter = new Intl.NumberFormat("ru-RU");
return formatter.format(value);
}
При каждом рендере компонент создаёт новый formatter.
const formatter = new Intl.NumberFormat("ru-RU");
function Price({ value }) {
return formatter.format(value);
}
useMemoЕсли локаль или настройки могут меняться:
import { useMemo } fr om "react";
function Price({ value, locale }) {
const formatter = useMemo(() => {
return new Intl.NumberFormat(locale, {
style: "currency",
currency: "USD"
});
}, [locale]);
return formatter.format(value);
}
formatter будет пересоздаваться только при изменении
locale.
const cache = new Map();
function getFormatter(locale) {
if (!cache.has(locale)) {
cache.set(locale, new Intl.NumberFormat(locale));
}
return cache.get(locale);
}
Использование:
const formatter = getFormatter("de-DE");
console.log(formatter.format(123456));
Обычно formatter зависит не только от locale.
Например:
const cache = new Map();
function getCurrencyFormatter(locale, currency) {
const key = `${locale}-${currency}`;
if (!cache.has(key)) {
cache.set(
key,
new Intl.NumberFormat(locale, {
style: "currency",
currency
})
);
}
return cache.get(key);
}
const cache = new Map();
function getFormatter(locale, options) {
const key = JSON.stringify([locale, options]);
if (!cache.has(key)) {
cache.set(
key,
new Intl.NumberFormat(locale, options)
);
}
return cache.get(key);
}
Использование:
const formatter = getFormatter("fr-FR", {
style: "currency",
currency: "EUR"
});
sortarray.sort((a, b) => {
const collator = new Intl.Collator("ru");
return collator.compare(a, b);
});
Во время сортировки comparator вызывается огромное количество раз.
Это приводит к множественному созданию
Intl.Collator.
const collator = new Intl.Collator("ru");
array.sort((a, b) => {
return collator.compare(a, b);
});
Intl.CollatorIntl.Collator особенно выигрывает от повторного
использования, потому что:
const collator = new Intl.Collator("sv");
const words = ["zebra", "åke", "äpple"];
words.sort(collator.compare);
console.log(words);
Intl.RelativeTimeFormatconst rtf = new Intl.RelativeTimeFormat("ru", {
numeric: "auto"
});
console.log(rtf.format(-1, "day"));
console.log(rtf.format(-2, "hour"));
console.log(rtf.format(5, "minute"));
Один formatter используется для множества операций.
Intl.ListFormatconst listFormatter = new Intl.ListFormat("ru", {
style: "long",
type: "conjunction"
});
console.log(
listFormatter.format([
"JavaScript",
"TypeScript",
"Rust"
])
);
Intl.PluralRulesconst pluralRules = new Intl.PluralRules("ru");
console.log(pluralRules.select(1));
console.log(pluralRules.select(2));
console.log(pluralRules.select(5));
Иногда formatter нужен глобально всему приложению.
export const currencyFormatter =
new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB"
});
Использование:
import { currencyFormatter } fr om "./formatters.js";
console.log(
currencyFormatter.format(100000)
);
В крупных приложениях часто создают отдельный сервис.
class FormatterManager {
constructor() {
this.cache = new Map();
}
getNumberFormatter(locale, options) {
const key = JSON.stringify([
locale,
options
]);
if (!this.cache.has(key)) {
this.cache.set(
key,
new Intl.NumberFormat(locale, options)
);
}
return this.cache.get(key);
}
}
Formatter создаётся только при первом запросе.
class PriceFormatter {
#formatter = null;
get formatter() {
if (!this.#formatter) {
this.#formatter =
new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB"
});
}
return this.#formatter;
}
format(value) {
return this.formatter.format(value);
}
}
В Node.js formatter может жить значительно дольше, чем в браузере.
Это особенно полезно:
const formatter =
new Intl.DateTimeFormat("ru-RU");
app.get("/events", (req, res) => {
const result = events.map(event => ({
...event,
date: formatter.format(event.date)
}));
res.json(result);
});
const cache = new Map();
Если ключей слишком много:
class FormatterCache {
constructor(lim it = 100) {
this.lim it = limit;
this.cache = new Map();
}
get(key, factory) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const value = factory();
this.cache.set(key, value);
if (this.cache.size > this.limit) {
const firstKey =
this.cache.keys().next().value;
this.cache.delete(firstKey);
}
return value;
}
}
console.log(
new Intl.NumberFormat("ru").format(1000)
);
Если форматирование выполняется один раз, дополнительный кэш не нужен.
Избыточное кэширование может:
Оптимизация оправдана там, где formatter используется многократно.
IntlПри создании formatter движок:
Повторное использование позволяет избежать повторной инициализации всех этих механизмов.
const formatterCache = new Map();
export function getNumberFormatter(
locale,
options
) {
const key = JSON.stringify([
locale,
options
]);
let formatter =
formatterCache.get(key);
if (!formatter) {
formatter =
new Intl.NumberFormat(
locale,
options
);
formatterCache.set(key, formatter);
}
return formatter;
}
Использование:
const formatter = getNumberFormatter(
"ja-JP",
{
style: "currency",
currency: "JPY"
}
);
console.log(formatter.format(100000));
console.time("bad");
for (let i = 0; i < 100000; i++) {
new Intl.NumberFormat("en-US")
.format(i);
}
console.timeEnd("bad");
const formatter =
new Intl.NumberFormat("en-US");
console.time("good");
for (let i = 0; i < 100000; i++) {
formatter.format(i);
}
console.timeEnd("good");
Разница во времени выполнения может быть очень значительной.
Экземпляры объектов Intl следует воспринимать как
тяжёлые конфигурируемые сервисы, а не как одноразовые утилиты.
Наиболее эффективная стратегия: