Почему стоит повторно использовать объекты форматирования, а не создавать новые?
Создание объектов форматирования Intl является затратным процессом, но повторное использование одного и того же экземпляра улучшает производительность
Введение
Когда вы создаёте объект форматирования Intl в JavaScript, браузер выполняет ресурсоёмкие операции для его настройки. Он анализирует ваши параметры, загружает данные локали с диска и строит внутренние структуры данных для форматирования. Если вы создаёте новый объект форматирования каждый раз, когда нужно отформатировать значение, вы повторяете эти ресурсоёмкие операции без необходимости.
Повторное использование объектов форматирования устраняет эту избыточную работу. Вы создаёте объект один раз и используете его многократно. Этот подход особенно важен в циклах, часто вызываемых функциях и высокопроизводительном коде, где требуется форматировать множество значений.
Разница в производительности между созданием новых объектов форматирования и повторным использованием существующих может быть значительной. В типичных сценариях повторное использование объектов форматирования может сократить время форматирования с сотен миллисекунд до всего нескольких миллисекунд.
Почему создание объектов форматирования является ресурсоёмким
Создание объекта форматирования Intl включает несколько затратных операций, которые выполняются внутри браузера.
Во-первых, браузер анализирует и проверяет предоставленные вами параметры. Он проверяет, являются ли идентификаторы локалей допустимыми, находятся ли числовые параметры в допустимом диапазоне и не сочетаются ли несовместимые параметры. Эта проверка требует анализа строк и операций поиска.
Во-вторых, браузер выполняет согласование локалей. Он берёт запрошенную вами локаль и находит наилучшее доступное соответствие среди локалей, поддерживаемых браузером. Это включает сравнение идентификаторов локалей и применение правил резервирования.
В-третьих, браузер загружает данные, специфичные для локали. Форматировщики дат нуждаются в названиях месяцев, дней недели и шаблонах форматирования для локали. Форматировщики чисел нуждаются в правилах группировки, разделителях десятичных знаков и символах цифр. Эти данные берутся из внутренней базы данных локалей браузера и должны быть загружены в память.
В-четвёртых, браузер строит внутренние структуры данных для форматирования. Он компилирует шаблоны форматирования в эффективные представления и настраивает конечные автоматы для обработки значений. Эти структуры сохраняются на протяжении всего времени существования объекта форматирования.
Вся эта работа выполняется каждый раз, когда вы создаёте объект форматирования. Если вы создаёте объект, используете его один раз и затем удаляете, вы тратите все эти ресурсы впустую.
Разница в производительности
Влияние на производительность при создании форматтеров становится заметным, когда вы форматируете множество значений.
Рассмотрим код, который форматирует список дат без повторного использования форматтера.
const dates = [
new Date('2024-01-15'),
new Date('2024-02-20'),
new Date('2024-03-10')
];
// Создает новый форматтер для каждой даты
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')
];
// Создаем форматтер один раз
const formatter = new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
// Повторно используем форматтер для каждой даты
dates.forEach(date => {
const formatted = formatter.format(date);
console.log(formatted);
});
Этот код создает один форматтер и использует его три раза. Для тысячи дат он все равно создает один форматтер и использует его тысячу раз.
Разница во времени между этими подходами увеличивается с количеством форматируемых значений. Форматирование тысячи дат с созданием тысячи форматтеров может занять более чем в 50 раз больше времени, чем форматирование с использованием одного повторно используемого форматтера.
Повторное использование форматтеров на уровне модуля
Самый простой способ повторно использовать форматтер — создать его один раз на уровне модуля и использовать его во всем модуле.
// Создаем форматтер на уровне модуля
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));
}
// Все функции используют один и тот же экземпляр форматтера
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('ru-RU', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
return function formatDate(date) {
return formatter.format(date);
};
}
const formatDate = createDateFormatter();
// Форматтер создается один раз при вызове createDateFormatter
// Каждый вызов formatDate повторно использует один и тот же форматтер
console.log(formatDate(new Date('2024-01-15')));
console.log(formatDate(new Date('2024-02-20')));
console.log(formatDate(new Date('2024-03-10')));
Этот подход полезен, когда вы хотите создать настроенный форматтер, который будет повторно использоваться, но не хотите предоставлять доступ к самому форматтеру.
Когда повторное использование форматтеров наиболее важно
Повторное использование форматтеров дает наибольшую выгоду в определенных сценариях.
Первый сценарий — это циклы. Если вы форматируете значения внутри цикла, создание нового форматтера на каждой итерации увеличивает затраты пропорционально количеству итераций.
// Неэффективно: создается N форматтеров
for (let i = 0; i < 1000; i++) {
const formatted = new Intl.NumberFormat('ru-RU').format(i);
processValue(formatted);
}
// Эффективно: создается 1 форматтер
const formatter = new Intl.NumberFormat('ru-RU');
for (let i = 0; i < 1000; i++) {
const formatted = formatter.format(i);
processValue(formatted);
}
Второй сценарий — часто вызываемые функции. Если функция форматирует значения и вызывается много раз, повторное использование форматтера позволяет избежать его создания при каждом вызове.
// Неэффективно: форматтер создается при каждом вызове
function formatCurrency(amount) {
const formatter = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
return formatter.format(amount);
}
// Эффективно: форматтер создается один раз
const currencyFormatter = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
function formatCurrency(amount) {
return currencyFormatter.format(amount);
}
Третий сценарий — обработка больших наборов данных. Когда вы форматируете сотни или тысячи значений, затраты на создание форматтеров становятся значительной частью общего времени.
// Неэффективно для больших наборов данных
function processRecords(records) {
return records.map(record => ({
date: new Intl.DateTimeFormat('ru-RU').format(record.date),
amount: new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
}).format(record.amount)
}));
}
// Эффективно для больших наборов данных
const dateFormatter = new Intl.DateTimeFormat('ru-RU');
const amountFormatter = new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency: 'RUB'
});
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) {
// Создаем ключ кэша из локали и опций
const key = JSON.stringify({ locale, options });
// Возвращаем кэшированный форматировщик, если он существует
if (formatterCache.has(key)) {
return formatterCache.get(key);
}
// Создаем новый форматировщик и добавляем его в кэш
const formatter = new Intl.NumberFormat(locale, options);
formatterCache.set(key, formatter);
return formatter;
}
// Первый вызов создает и кэширует форматировщик
const formatter1 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter1.format(42.50));
// Второй вызов использует кэшированный форматировщик
const formatter2 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter2.format(99.99));
// Различные опции создают и кэшируют новый форматировщик
const formatter3 = getNumberFormatter('en-US', { style: 'percent' });
console.log(formatter3.format(0.42));
Этот подход позволяет вам получить преимущества повторного использования форматировщиков, даже если вам нужны разные конфигурации форматирования в разных частях вашего кода.
Оптимизации современных браузеров
Современные JavaScript-движки оптимизировали создание Intl форматировщиков, чтобы уменьшить затраты на производительность. Создание форматировщиков сегодня быстрее, чем в старых браузерах.
Однако повторное использование форматировщиков остается лучшей практикой. Даже с оптимизациями создание форматировщика все еще дороже, чем вызов метода format() у уже существующего форматировщика. Разница в стоимости меньше, чем раньше, но она все же существует.
В высокопроизводительном коде, коде, который выполняется в циклах, и коде, который обрабатывает большие наборы данных, повторное использование форматировщиков продолжает обеспечивать ощутимые преимущества. Оптимизация создания форматировщиков не устраняет ценность их повторного использования.
Основные выводы
Создание объектов форматирования Intl является затратным процессом, так как браузеру необходимо разобрать параметры, выполнить согласование локалей, загрузить данные локалей и построить внутренние структуры данных. Эта работа выполняется каждый раз при создании нового объекта форматирования.
Повторное использование экземпляров форматирования позволяет избежать повторения этой работы. Вы создаёте объект форматирования один раз и многократно вызываете его метод format(). Это сокращает время, затрачиваемое на операции форматирования.
Повторное использование объектов форматирования особенно важно в циклах, часто вызываемых функциях и коде, который обрабатывает большие наборы данных. В таких сценариях стоимость создания объектов форматирования может составлять значительную часть общего времени выполнения.
Самый простой способ повторного использования — создание объектов форматирования на уровне модуля. Для более сложных сценариев можно использовать замыкания или кэширование на основе параметров конфигурации.