Почему стоит переиспользовать форматтеры, а не создавать новые?
Создание Intl-форматтеров требует ресурсов, а повторное использование одного экземпляра ускоряет работу
Введение
Когда вы создаёте Intl-форматтер в JavaScript, браузер выполняет ресурсоёмкие операции для его инициализации. Он разбирает ваши параметры, загружает языковые данные с диска и строит внутренние структуры для форматирования. Если вы создаёте новый форматтер каждый раз, когда нужно отформатировать значение, вы зря повторяете эти трудозатраты.
Переиспользование экземпляров форматтеров избавляет от этой лишней работы. Вы создаёте форматтер один раз и используете его многократно. Такой подход особенно важен в циклах, часто вызываемых функциях и высокопроизводительном коде, где приходится форматировать много значений.
Разница в производительности между созданием новых форматтеров и повторным использованием уже существующих может быть значительной. В типичных случаях переиспользование форматтеров сокращает время форматирования с сотен миллисекунд до всего нескольких миллисекунд.
Почему создание форматтеров требует ресурсов
Создание Intl-форматтера включает несколько затратных операций, которые происходят внутри браузера.
Сначала браузер разбирает и проверяет переданные вами параметры. Он убеждается, что идентификаторы локалей корректны, числовые параметры находятся в допустимых пределах, а несовместимые параметры не используются вместе. Для этого требуется разбор строк и операции поиска.
Затем браузер выполняет согласование локали. Он берёт запрошенную вами локаль и ищет наилучшее совпадение среди поддерживаемых локалей. Для этого сравниваются идентификаторы локалей и применяются правила подстановки.
Далее браузер загружает данные, специфичные для локали. Форматтерам дат нужны названия месяцев, дней недели и шаблоны форматирования для выбранной локали. Форматтерам чисел — правила группировки, разделители и символы цифр. Эти данные берутся из внутренней базы браузера и должны быть загружены в память.
В-четвёртых, браузер строит внутренние структуры данных для форматирования. Он компилирует шаблоны форматирования в эффективные представления и настраивает конечные автоматы для обработки значений. Эти структуры существуют всё время жизни форматтера.
Всё это происходит каждый раз, когда вы создаёте форматтер. Если вы создали форматтер, использовали его один раз и выбросили, вся эта подготовительная работа оказывается напрасной.
Разница в производительности
Влияние на производительность при повторном создании форматтеров становится заметным, когда нужно отформатировать много значений.
Рассмотрим код, который форматирует список дат без повторного использования форматтера.
const dates = [
new Date('2024-01-15'),
new Date('2024-02-20'),
new Date('2024-03-10')
];
// Creates a new formatter for each date
dates.forEach(date => {
const formatted = date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
console.log(formatted);
});
Метод toLocaleDateString() внутри себя создаёт новый экземпляр DateTimeFormat для каждой форматируемой даты. Для трёх дат будет создано три форматтера. Для тысячи дат — тысяча форматтеров.
Сравните это с кодом, который создаёт один форматтер и использует его повторно.
const dates = [
new Date('2024-01-15'),
new Date('2024-02-20'),
new Date('2024-03-10')
];
// Create formatter once
const formatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Reuse formatter for each date
dates.forEach(date => {
const formatted = formatter.format(date);
console.log(formatted);
});
Этот код создаёт один форматтер и использует его три раза. Для тысячи дат он всё равно создаёт только один форматтер и использует его тысячу раз.
Разница во времени между этими подходами увеличивается с количеством форматируемых значений. Форматирование тысячи дат с созданием тысячи форматтеров может занять в 50 раз больше времени, чем с одним повторно используемым форматтером.
Повторное использование форматтеров на уровне модуля
Самый простой способ повторно использовать форматтер — создать его один раз на уровне модуля и использовать во всём модуле.
// Create formatter at module scope
const dateFormatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
function formatDate(date) {
return dateFormatter.format(date);
}
function formatDates(dates) {
return dates.map(date => dateFormatter.format(date));
}
// All functions share the same formatter instance
console.log(formatDate(new Date()));
console.log(formatDates([new Date(), new Date()]));
Этот подход хорошо работает, если вы форматируете значения одинаково по всему коду. Форматтер живёт всё время работы приложения, и любая функция может использовать этот экземпляр.
Тот же подход работает для форматтеров чисел, списков и всех других Intl-форматтеров.
const numberFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
const listFormatter = new Intl.ListFormat('en-US', {
style: 'long',
type: 'conjunction'
});
function formatPrice(amount) {
return numberFormatter.format(amount);
}
function formatNames(names) {
return listFormatter.format(names);
}
Повторное использование форматтеров в функциях
Когда вам нужны разные параметры форматирования в разных частях кода, вы можете создавать форматтеры внутри функций и использовать замыкания для их сохранения.
function createDateFormatter() {
const formatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
return function formatDate(date) {
return formatter.format(date);
};
}
const formatDate = createDateFormatter();
// The formatter is created once when you call createDateFormatter
// Each call to formatDate reuses the same formatter
console.log(formatDate(new Date('2024-01-15')));
console.log(formatDate(new Date('2024-02-20')));
console.log(formatDate(new Date('2024-03-10')));
Этот подход полезен, если вы хотите создать настроенный форматтер для повторного использования, но не хотите делать сам форматтер доступным извне.
Когда особенно важно переиспользовать форматтеры
Переиспользование форматтеров даёт наибольший эффект в определённых сценариях.
Первый сценарий — это циклы. Если вы форматируете значения внутри цикла, создание нового форматтера на каждой итерации увеличивает затраты пропорционально количеству итераций.
// Inefficient: creates N formatters
for (let i = 0; i < 1000; i++) {
const formatted = new Intl.NumberFormat('en-US').format(i);
processValue(formatted);
}
// Efficient: creates 1 formatter
const formatter = new Intl.NumberFormat('en-US');
for (let i = 0; i < 1000; i++) {
const formatted = formatter.format(i);
processValue(formatted);
}
Второй сценарий — часто вызываемые функции. Если функция форматирует значения и вызывается много раз, переиспользование форматтера избавляет от необходимости создавать его при каждом вызове.
// Inefficient: creates formatter on every call
function formatCurrency(amount) {
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
return formatter.format(amount);
}
// Efficient: creates formatter once
const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
function formatCurrency(amount) {
return currencyFormatter.format(amount);
}
Третий сценарий — обработка больших наборов данных. Когда вы форматируете сотни или тысячи значений, затраты на создание форматтеров становятся значительной частью общего времени.
// Inefficient for large datasets
function processRecords(records) {
return records.map(record => ({
date: new Intl.DateTimeFormat('en-US').format(record.date),
amount: new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(record.amount)
}));
}
// Efficient for large datasets
const dateFormatter = new Intl.DateTimeFormat('en-US');
const amountFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
function processRecords(records) {
return records.map(record => ({
date: dateFormatter.format(record.date),
amount: amountFormatter.format(record.amount)
}));
}
В этих случаях повторное использование форматтеров сокращает время на операции форматирования и повышает отзывчивость приложения.
Кэширование форматтеров с разными параметрами
Если вам нужно использовать форматтеры с множеством разных комбинаций параметров, вы можете кэшировать их по конфигурации.
const formatterCache = new Map();
function getNumberFormatter(locale, options) {
// Create a cache key from locale and options
const key = JSON.stringify({ locale, options });
// Return cached formatter if it exists
if (formatterCache.has(key)) {
return formatterCache.get(key);
}
// Create new formatter and cache it
const formatter = new Intl.NumberFormat(locale, options);
formatterCache.set(key, formatter);
return formatter;
}
// First call creates and caches formatter
const formatter1 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter1.format(42.50));
// Second call reuses cached formatter
const formatter2 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter2.format(99.99));
// Different options create and cache a new formatter
const formatter3 = getNumberFormatter('en-US', { style: 'percent' });
console.log(formatter3.format(0.42));
Этот подход позволяет получить преимущества повторного использования даже при необходимости разных настроек форматирования в разных частях кода.
Оптимизации современных браузеров
Современные движки JavaScript оптимизировали создание Intl-форматтеров, чтобы снизить издержки по производительности. Сейчас создание форматтеров происходит быстрее, чем в старых браузерах.
Тем не менее, повторное использование форматтеров по-прежнему считается лучшей практикой. Даже с оптимизациями создание форматтера всё ещё обходится дороже, чем вызов метода format() у уже существующего форматтера. Разница в стоимости стала меньше, чем раньше, но она всё ещё есть.
В высокопроизводительном коде, коде, который работает в циклах, и коде, обрабатывающем большие объёмы данных, повторное использование форматтеров продолжает давать заметные преимущества. Оптимизация создания форматтеров не отменяет пользу от их повторного использования.
Основные выводы
Создание Intl-форматтеров требует ресурсов, потому что браузеру нужно разобрать параметры, провести согласование локали, загрузить языковые данные и построить внутренние структуры. Всё это происходит каждый раз при создании форматтера.
Переиспользование экземпляров форматтеров позволяет избежать повторения этой работы. Вы создаёте форматтер один раз и многократно вызываете его метод format(). Это сокращает время, затрачиваемое на операции форматирования.
Повторное использование форматтеров особенно важно в циклах, часто вызываемых функциях и коде, который обрабатывает большие объёмы данных. В таких случаях стоимость создания форматтеров может стать значительной частью общего времени выполнения.
Самый простой способ повторного использования — создавать форматтеры на уровне модуля. Для более сложных случаев можно использовать замыкания или кэширование на основе параметров конфигурации.