¿Por qué deberías reutilizar formateadores en lugar de crear nuevos?
Crear formateadores Intl es costoso, pero reutilizar la misma instancia de formateador mejora el rendimiento
Introducción
Cuando creas un formateador Intl en JavaScript, el navegador realiza operaciones costosas para configurar la instancia del formateador. Analiza tus opciones, carga datos de configuración regional desde el disco y construye estructuras de datos internas para el formateo. Si creas un nuevo formateador cada vez que necesitas formatear un valor, repites este trabajo costoso innecesariamente.
Reutilizar instancias de formateadores elimina este trabajo repetido. Creas el formateador una vez y lo usas muchas veces. Este patrón es particularmente importante en bucles, funciones llamadas frecuentemente y código de alto rendimiento donde formateas muchos valores.
La diferencia de rendimiento entre crear nuevos formateadores y reutilizar los existentes puede ser sustancial. En escenarios típicos, reutilizar formateadores puede reducir el tiempo de formateo de cientos de milisegundos a solo unos pocos milisegundos.
Por qué crear formateadores es costoso
Crear un formateador Intl implica varias operaciones costosas que ocurren dentro del navegador.
Primero, el navegador analiza y valida las opciones que proporcionas. Verifica que los identificadores de configuración regional sean válidos, que las opciones numéricas estén dentro del rango y que no se combinen opciones incompatibles. Esta validación requiere análisis de cadenas y operaciones de búsqueda.
Segundo, el navegador realiza la negociación de configuración regional. Toma la configuración regional solicitada y encuentra la mejor coincidencia disponible entre las configuraciones regionales que el navegador admite. Esto implica comparar identificadores de configuración regional y aplicar reglas de respaldo.
Tercero, el navegador carga datos específicos de la configuración regional. Los formateadores de fechas necesitan nombres de meses, nombres de días y patrones de formateo para la configuración regional. Los formateadores de números necesitan reglas de agrupación, separadores decimales y caracteres de dígitos. Estos datos provienen de la base de datos de configuración regional interna del navegador y deben cargarse en memoria.
En cuarto lugar, el navegador construye estructuras de datos internas para el formateo. Compila patrones de formateo en representaciones eficientes y configura máquinas de estado para procesar valores. Estas estructuras persisten durante la vida útil del formateador.
Todo este trabajo ocurre cada vez que creas un formateador. Si creas un formateador, lo usas una vez y lo descartas, desperdicias todo ese trabajo de configuración.
La diferencia de rendimiento
El impacto en el rendimiento de recrear formateadores se vuelve visible cuando formateas muchos valores.
Considera código que formatea una lista de fechas sin reutilizar el formateador.
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);
});
El método toLocaleDateString() crea una nueva instancia de DateTimeFormat internamente para cada fecha que formatea. Para tres fechas, esto crea tres formateadores. Para mil fechas, crea mil formateadores.
Compara esto con código que crea un formateador y lo reutiliza.
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);
});
Este código crea un formateador y lo usa tres veces. Para mil fechas, todavía crea un formateador y lo usa mil veces.
La diferencia de tiempo entre estos enfoques crece con el número de valores que formateas. Formatear mil fechas creando mil formateadores puede tardar más de 50 veces más que formatearlas con un formateador reutilizado.
Reutilización de formateadores en el ámbito del módulo
La forma más simple de reutilizar un formateador es crearlo una vez en el ámbito del módulo y usarlo en todo tu módulo.
// 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()]));
Este patrón funciona bien cuando formateas valores de la misma manera en todo tu código. El formateador vive durante la vida útil de tu aplicación, y cada función que lo necesite puede usar la misma instancia.
El mismo patrón funciona para formateadores de números, formateadores de listas y todos los demás formateadores 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);
}
Reutilización de formateadores en funciones
Cuando necesitas diferentes opciones de formateo en distintas partes de tu código, puedes crear formateadores dentro de funciones y confiar en closures para preservarlos.
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')));
Este patrón es útil cuando quieres crear un formateador configurado que se reutilice pero no quieres exponer el formateador en sí.
Cuándo la reutilización de formateadores importa más
Reutilizar formateadores proporciona el mayor beneficio en escenarios específicos.
El primer escenario son los bucles. Si formateas valores dentro de un bucle, crear un nuevo formateador en cada iteración multiplica el coste por el número de iteraciones.
// 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);
}
El segundo escenario son las funciones frecuentemente llamadas. Si una función formatea valores y se llama muchas veces, la reutilización de formateadores evita recrear el formateador en cada llamada.
// 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);
}
El tercer escenario es el procesamiento de grandes conjuntos de datos. Cuando formateas cientos o miles de valores, el coste de configuración de crear formateadores se convierte en una porción significativa del tiempo total.
// 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)
}));
}
En estos escenarios, reutilizar formateadores reduce el tiempo dedicado a operaciones de formateo y mejora la capacidad de respuesta de la aplicación.
Almacenamiento en caché de formateadores con diferentes opciones
Cuando necesitas usar formateadores con muchas combinaciones de opciones diferentes, puedes almacenar en caché los formateadores según su configuración.
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));
Este patrón te permite obtener los beneficios de la reutilización de formateadores incluso cuando necesitas diferentes configuraciones de formateo en distintas partes de tu código.
Optimizaciones de navegadores modernos
Los motores JavaScript modernos han optimizado la creación de formateadores Intl para reducir el coste de rendimiento. Crear formateadores es más rápido hoy que en navegadores antiguos.
Sin embargo, reutilizar formateadores sigue siendo una buena práctica. Incluso con optimizaciones, crear un formateador sigue siendo más costoso que llamar al método format() en un formateador existente. La diferencia de coste es menor que antes, pero aún existe.
En código de alto rendimiento, código que se ejecuta en bucles y código que procesa grandes conjuntos de datos, la reutilización de formateadores continúa proporcionando beneficios medibles. La optimización de la creación de formateadores no elimina el valor de reutilizar formateadores.
Conclusiones clave
Crear formateadores Intl es costoso porque el navegador debe analizar opciones, realizar negociación de configuración regional, cargar datos de configuración regional y construir estructuras de datos internas. Este trabajo ocurre cada vez que creas un formateador.
Reutilizar instancias de formateadores evita repetir este trabajo. Creas el formateador una vez y llamas a su método format() muchas veces. Esto reduce el tiempo dedicado a operaciones de formateo.
La reutilización de formateadores importa más en bucles, funciones frecuentemente llamadas y código que procesa grandes conjuntos de datos. En estos escenarios, el coste de crear formateadores puede convertirse en una porción significativa del tiempo total de ejecución.
El patrón de reutilización más simple es crear formateadores a nivel de módulo. Para escenarios más complejos, puedes usar closures o almacenamiento en caché basado en opciones de configuración.