Warum sollten Sie Formatierer wiederverwenden, anstatt neue zu erstellen?

Das Erstellen von Intl-Formatierern ist aufwendig, aber die Wiederverwendung derselben Formatierer-Instanz verbessert die Performance

Einführung

Wenn Sie einen Intl-Formatierer in JavaScript erstellen, führt der Browser aufwendige Operationen durch, um die Formatierer-Instanz einzurichten. Er parst Ihre Optionen, lädt Locale-Daten von der Festplatte und baut interne Datenstrukturen für die Formatierung auf. Wenn Sie jedes Mal einen neuen Formatierer erstellen, wenn Sie einen Wert formatieren müssen, wiederholen Sie diese aufwendige Arbeit unnötigerweise.

Die Wiederverwendung von Formatierer-Instanzen eliminiert diese wiederholte Arbeit. Sie erstellen den Formatierer einmal und verwenden ihn viele Male. Dieses Muster ist besonders wichtig in Schleifen, häufig aufgerufenen Funktionen und hochperformantem Code, wo Sie viele Werte formatieren.

Der Performance-Unterschied zwischen dem Erstellen neuer Formatierer und der Wiederverwendung bestehender kann erheblich sein. In typischen Szenarien kann die Wiederverwendung von Formatierern die Formatierungszeit von Hunderten von Millisekunden auf nur wenige Millisekunden reduzieren.

Warum das Erstellen von Formatierern aufwendig ist

Das Erstellen eines Intl-Formatierers umfasst mehrere kostspielige Operationen, die im Browser ablaufen.

Zunächst parst und validiert der Browser die von Ihnen bereitgestellten Optionen. Er prüft, ob Locale-Identifikatoren gültig sind, ob numerische Optionen im zulässigen Bereich liegen und ob inkompatible Optionen nicht kombiniert werden. Diese Validierung erfordert String-Parsing und Lookup-Operationen.

Zweitens führt der Browser eine Locale-Verhandlung durch. Er nimmt Ihre angeforderte Locale und findet die beste verfügbare Übereinstimmung aus den vom Browser unterstützten Locales. Dies umfasst den Vergleich von Locale-Identifikatoren und die Anwendung von Fallback-Regeln.

Drittens lädt der Browser locale-spezifische Daten. Datumsformatierer benötigen Monatsnamen, Tagesnamen und Formatierungsmuster für die Locale. Zahlenformatierer benötigen Gruppierungsregeln, Dezimaltrennzeichen und Ziffernzeichen. Diese Daten stammen aus der internen Locale-Datenbank des Browsers und müssen in den Speicher geladen werden.

Viertens erstellt der Browser interne Datenstrukturen für die Formatierung. Er kompiliert Formatierungsmuster in effiziente Darstellungen und richtet Zustandsautomaten für die Verarbeitung von Werten ein. Diese Strukturen bleiben für die Lebensdauer des Formatierers bestehen.

All diese Arbeit findet jedes Mal statt, wenn Sie einen Formatierer erstellen. Wenn Sie einen Formatierer erstellen, ihn einmal verwenden und dann verwerfen, verschwenden Sie die gesamte Einrichtungsarbeit.

Der Leistungsunterschied

Die Leistungsauswirkung der Neuerstellung von Formatierern wird sichtbar, wenn Sie viele Werte formatieren.

Betrachten Sie Code, der eine Liste von Datumsangaben formatiert, ohne den Formatierer wiederzuverwenden.

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);
});

Die Methode toLocaleDateString() erstellt intern für jedes Datum, das sie formatiert, eine neue Instanz von DateTimeFormat. Für drei Datumsangaben werden drei Formatierer erstellt. Für tausend Datumsangaben werden tausend Formatierer erstellt.

Vergleichen Sie dies mit Code, der einen Formatierer erstellt und ihn wiederverwendet.

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);
});

Dieser Code erstellt einen Formatierer und verwendet ihn dreimal. Für tausend Datumsangaben erstellt er immer noch einen Formatierer und verwendet ihn tausendmal.

Der Zeitunterschied zwischen diesen Ansätzen wächst mit der Anzahl der Werte, die Sie formatieren. Das Formatieren von tausend Datumsangaben durch Erstellen von tausend Formatierern kann über 50-mal länger dauern als das Formatieren mit einem wiederverwendeten Formatierer.

Wiederverwendung von Formatierern auf Modulebene

Der einfachste Weg, einen Formatierer wiederzuverwenden, besteht darin, ihn einmal auf Modulebene zu erstellen und ihn in Ihrem gesamten Modul zu verwenden.

// 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()]));

Dieses Muster funktioniert gut, wenn Sie Werte in Ihrem gesamten Code auf die gleiche Weise formatieren. Der Formatierer existiert für die Lebensdauer Ihrer Anwendung, und jede Funktion, die ihn benötigt, kann dieselbe Instanz verwenden.

Das gleiche Muster funktioniert für Zahlenformatter, Listenformatter und alle anderen Intl-Formatter.

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);
}

Formatter in Funktionen wiederverwenden

Wenn Sie in verschiedenen Teilen Ihres Codes unterschiedliche Formatierungsoptionen benötigen, können Sie Formatter innerhalb von Funktionen erstellen und sich auf Closures verlassen, um sie zu erhalten.

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')));

Dieses Muster ist nützlich, wenn Sie einen konfigurierten Formatter erstellen möchten, der wiederverwendet wird, aber den Formatter selbst nicht offenlegen möchten.

Wann Formatter-Wiederverwendung am wichtigsten ist

Die Wiederverwendung von Formattern bietet in bestimmten Szenarien den größten Nutzen.

Das erste Szenario sind Schleifen. Wenn Sie Werte innerhalb einer Schleife formatieren, multipliziert das Erstellen eines neuen Formatters bei jeder Iteration die Kosten mit der Anzahl der Iterationen.

// 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);
}

Das zweite Szenario sind häufig aufgerufene Funktionen. Wenn eine Funktion Werte formatiert und viele Male aufgerufen wird, vermeidet die Formatter-Wiederverwendung das Neuerstellen des Formatters bei jedem Aufruf.

// 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);
}

Das dritte Szenario ist die Verarbeitung großer Datensätze. Wenn Sie Hunderte oder Tausende von Werten formatieren, werden die Setup-Kosten für das Erstellen von Formattern zu einem erheblichen Teil der Gesamtzeit.

// 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)
  }));
}

In diesen Szenarien reduziert die Wiederverwendung von Formattern die für Formatierungsoperationen aufgewendete Zeit und verbessert die Reaktionsfähigkeit der Anwendung.

Formatter mit unterschiedlichen Optionen cachen

Wenn Sie Formatter mit vielen verschiedenen Optionskombinationen verwenden müssen, können Sie Formatter basierend auf ihrer Konfiguration cachen.

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));

Dieses Muster ermöglicht es Ihnen, die Vorteile der Formatter-Wiederverwendung zu nutzen, auch wenn Sie in verschiedenen Teilen Ihres Codes unterschiedliche Formatierungskonfigurationen benötigen.

Moderne Browser-Optimierungen

Moderne JavaScript-Engines haben die Erstellung von Intl-Formattern optimiert, um die Performance-Kosten zu reduzieren. Das Erstellen von Formattern ist heute schneller als in älteren Browsern.

Die Wiederverwendung von Formattern bleibt jedoch eine Best Practice. Selbst mit Optimierungen ist das Erstellen eines Formatters immer noch aufwendiger als das Aufrufen der format()-Methode auf einem bestehenden Formatter. Der Kostenunterschied ist kleiner als früher, aber er existiert weiterhin.

In hochperformantem Code, Code, der in Schleifen läuft, und Code, der große Datensätze verarbeitet, bietet die Wiederverwendung von Formattern weiterhin messbare Vorteile. Die Optimierung der Formatter-Erstellung eliminiert nicht den Wert der Wiederverwendung von Formattern.

Wichtigste Erkenntnisse

Das Erstellen von Intl-Formattern ist aufwendig, weil der Browser Optionen parsen, Locale-Verhandlung durchführen, Locale-Daten laden und interne Datenstrukturen aufbauen muss. Diese Arbeit erfolgt jedes Mal, wenn Sie einen Formatter erstellen.

Die Wiederverwendung von Formatter-Instanzen vermeidet die Wiederholung dieser Arbeit. Sie erstellen den Formatter einmal und rufen seine format()-Methode viele Male auf. Dies reduziert die für Formatierungsoperationen aufgewendete Zeit.

Die Wiederverwendung von Formattern ist am wichtigsten in Schleifen, häufig aufgerufenen Funktionen und Code, der große Datensätze verarbeitet. In diesen Szenarien können die Kosten für das Erstellen von Formattern zu einem erheblichen Teil der gesamten Ausführungszeit werden.

Das einfachste Wiederverwendungsmuster ist das Erstellen von Formattern auf Modulebene. Für komplexere Szenarien können Sie Closures oder Caching basierend auf Konfigurationsoptionen verwenden.