Pourquoi devriez-vous réutiliser les formateurs au lieu d'en créer de nouveaux ?
La création de formateurs Intl est coûteuse, mais la réutilisation de la même instance de formateur améliore les performances
Introduction
Lorsque vous créez un formateur Intl en JavaScript, le navigateur effectue des opérations coûteuses pour configurer l'instance du formateur. Il analyse vos options, charge les données de locale depuis le disque et construit des structures de données internes pour le formatage. Si vous créez un nouveau formateur chaque fois que vous devez formater une valeur, vous répétez inutilement ce travail coûteux.
La réutilisation des instances de formateur élimine ce travail répété. Vous créez le formateur une fois et l'utilisez plusieurs fois. Ce pattern est particulièrement important dans les boucles, les fonctions fréquemment appelées et le code haute performance où vous formatez de nombreuses valeurs.
La différence de performance entre la création de nouveaux formateurs et la réutilisation de ceux existants peut être substantielle. Dans des scénarios typiques, la réutilisation des formateurs peut réduire le temps de formatage de centaines de millisecondes à seulement quelques millisecondes.
Pourquoi la création de formateurs est coûteuse
La création d'un formateur Intl implique plusieurs opérations coûteuses qui se produisent à l'intérieur du navigateur.
Premièrement, le navigateur analyse et valide les options que vous fournissez. Il vérifie que les identifiants de locale sont valides, que les options numériques sont dans la plage autorisée et que des options incompatibles ne sont pas combinées. Cette validation nécessite l'analyse de chaînes et des opérations de recherche.
Deuxièmement, le navigateur effectue la négociation de locale. Il prend la locale que vous avez demandée et trouve la meilleure correspondance disponible parmi les locales que le navigateur prend en charge. Cela implique de comparer les identifiants de locale et d'appliquer des règles de repli.
Troisièmement, le navigateur charge les données spécifiques à la locale. Les formateurs de date ont besoin des noms de mois, des noms de jours et des patterns de formatage pour la locale. Les formateurs de nombres ont besoin des règles de regroupement, des séparateurs décimaux et des caractères de chiffres. Ces données proviennent de la base de données de locale interne du navigateur et doivent être chargées en mémoire.
Quatrièmement, le navigateur construit des structures de données internes pour le formatage. Il compile les modèles de formatage en représentations efficaces et configure des machines à états pour traiter les valeurs. Ces structures persistent pendant toute la durée de vie du formateur.
Tout ce travail se produit à chaque fois que vous créez un formateur. Si vous créez un formateur, l'utilisez une fois et le supprimez, vous gaspillez tout ce travail de configuration.
La différence de performance
L'impact sur les performances de la recréation des formateurs devient visible lorsque vous formatez de nombreuses valeurs.
Considérez un code qui formate une liste de dates sans réutiliser le formateur.
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);
});
La méthode toLocaleDateString() crée une nouvelle instance DateTimeFormat en interne pour chaque date qu'elle formate. Pour trois dates, cela crée trois formateurs. Pour mille dates, cela crée mille formateurs.
Comparez cela à un code qui crée un formateur et le réutilise.
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);
});
Ce code crée un formateur et l'utilise trois fois. Pour mille dates, il crée toujours un formateur et l'utilise mille fois.
La différence de temps entre ces approches augmente avec le nombre de valeurs que vous formatez. Formater mille dates en créant mille formateurs peut prendre plus de 50 fois plus de temps que de les formater avec un seul formateur réutilisé.
Réutilisation des formateurs au niveau du module
La façon la plus simple de réutiliser un formateur est de le créer une fois au niveau du module et de l'utiliser dans tout votre module.
// 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()]));
Ce modèle fonctionne bien lorsque vous formatez les valeurs de la même manière dans tout votre code. Le formateur vit pendant toute la durée de vie de votre application, et chaque fonction qui en a besoin peut utiliser la même instance.
Le même pattern fonctionne pour les formateurs de nombres, les formateurs de listes et tous les autres formateurs 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);
}
Réutiliser les formateurs dans les fonctions
Lorsque vous avez besoin d'options de formatage différentes dans différentes parties de votre code, vous pouvez créer des formateurs à l'intérieur des fonctions et vous appuyer sur les closures pour les préserver.
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')));
Ce pattern est utile lorsque vous souhaitez créer un formateur configuré qui sera réutilisé mais que vous ne voulez pas exposer le formateur lui-même.
Quand la réutilisation des formateurs est la plus importante
La réutilisation des formateurs offre le plus d'avantages dans des scénarios spécifiques.
Le premier scénario concerne les boucles. Si vous formatez des valeurs à l'intérieur d'une boucle, créer un nouveau formateur à chaque itération multiplie le coût par le nombre d'itérations.
// 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);
}
Le deuxième scénario concerne les fonctions fréquemment appelées. Si une fonction formate des valeurs et est appelée de nombreuses fois, la réutilisation du formateur évite de recréer le formateur à chaque appel.
// 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);
}
Le troisième scénario concerne le traitement de grands ensembles de données. Lorsque vous formatez des centaines ou des milliers de valeurs, le coût d'initialisation de la création des formateurs devient une portion significative du temps 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)
}));
}
Dans ces scénarios, la réutilisation des formateurs réduit le temps consacré aux opérations de formatage et améliore la réactivité de l'application.
Mettre en cache les formateurs avec différentes options
Lorsque vous devez utiliser des formateurs avec de nombreuses combinaisons d'options différentes, vous pouvez mettre en cache les formateurs en fonction de leur configuration.
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));
Ce pattern vous permet d'obtenir les avantages de la réutilisation des formateurs même lorsque vous avez besoin de configurations de formatage différentes dans différentes parties de votre code.
Optimisations des navigateurs modernes
Les moteurs JavaScript modernes ont optimisé la création des formateurs Intl pour réduire le coût en performance. La création de formateurs est plus rapide aujourd'hui qu'elle ne l'était dans les navigateurs plus anciens.
Cependant, la réutilisation des formateurs reste une bonne pratique. Même avec les optimisations, la création d'un formateur est toujours plus coûteuse que l'appel de la méthode format() sur un formateur existant. La différence de coût est plus faible qu'auparavant, mais elle existe toujours.
Dans le code haute performance, le code qui s'exécute en boucles et le code qui traite de grands ensembles de données, la réutilisation des formateurs continue de fournir des avantages mesurables. L'optimisation de la création de formateurs n'élimine pas la valeur de la réutilisation des formateurs.
Points clés à retenir
La création de formateurs Intl est coûteuse car le navigateur doit analyser les options, effectuer la négociation de locale, charger les données de locale et construire des structures de données internes. Ce travail se produit chaque fois que vous créez un formateur.
La réutilisation des instances de formateur évite de répéter ce travail. Vous créez le formateur une fois et appelez sa méthode format() plusieurs fois. Cela réduit le temps consacré aux opérations de formatage.
La réutilisation des formateurs est particulièrement importante dans les boucles, les fonctions fréquemment appelées et le code qui traite de grands ensembles de données. Dans ces scénarios, le coût de création des formateurs peut devenir une partie significative du temps d'exécution total.
Le pattern de réutilisation le plus simple consiste à créer des formateurs au niveau du module. Pour des scénarios plus complexes, vous pouvez utiliser des closures ou une mise en cache basée sur les options de configuration.