Warum sollten Sie Formatierer wiederverwenden, anstatt neue zu erstellen?

Das Erstellen von Intl-Formatierern ist rechenintensiv, aber die Wiederverwendung derselben Formatierer-Instanz verbessert die Leistung

Einführung

Wenn Sie einen Intl-Formatierer in JavaScript erstellen, führt der Browser aufwendige Operationen durch, um die Formatierer-Instanz einzurichten. Er analysiert Ihre Optionen, lädt Gebietsschema-Daten von der Festplatte und erstellt interne Datenstrukturen für die Formatierung. Wenn Sie bei jedem Formatierungsbedarf einen neuen Formatierer erstellen, wiederholen Sie diese aufwendige Arbeit unnötigerweise.

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

Der Leistungsunterschied zwischen dem Erstellen neuer Formatierer und dem Wiederverwenden 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 stattfinden.

Erstens analysiert und validiert der Browser die von Ihnen bereitgestellten Optionen. Er überprüft, ob Gebietsschema-Kennungen gültig sind, ob numerische Optionen im zulässigen Bereich liegen und ob inkompatible Optionen nicht kombiniert werden. Diese Validierung erfordert String-Parsing und Nachschlageoperationen.

Zweitens führt der Browser eine Gebietsschema-Verhandlung durch. Er nimmt Ihr angefordertes Gebietsschema und findet die beste verfügbare Übereinstimmung aus den vom Browser unterstützten Gebietsschemas. Dies beinhaltet den Vergleich von Gebietsschema-Kennungen und die Anwendung von Fallback-Regeln.

Drittens lädt der Browser gebietsschemaspezifische Daten. Datumsformatierer benötigen Monatsnamen, Tagesnamen und Formatierungsmuster für das Gebietsschema. Zahlenformatierer benötigen Gruppierungsregeln, Dezimaltrennzeichen und Ziffernzeichen. Diese Daten stammen aus der internen Gebietsschema-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 Zustandsmaschinen für die Verarbeitung von Werten ein. Diese Strukturen bleiben für die Lebensdauer des Formatierers bestehen.

All diese Arbeit geschieht jedes Mal, 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 Neuinstanziierung 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')
];

// Erstellt einen neuen Formatierer für jedes Datum
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 zu formatierende Datum eine neue DateTimeFormat-Instanz. 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 wiederverwendet.

const dates = [
  new Date('2024-01-15'),
  new Date('2024-02-20'),
  new Date('2024-03-10')
];

// Formatierer einmal erstellen
const formatter = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

// Formatierer für jedes Datum wiederverwenden
dates.forEach(date => {
  const formatted = formatter.format(date);
  console.log(formatted);
});

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

Der Zeitunterschied zwischen diesen Ansätzen wächst mit der Anzahl der zu formatierenden Werte. Die Formatierung von tausend Datumsangaben durch Erstellung von tausend Formatierern kann mehr als 50-mal länger dauern als die Formatierung mit einem wiederverwendeten Formatierer.

Wiederverwendung von Formatierern auf Modulebene

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

// Formatierer auf Modulebene erstellen
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));
}

// Alle Funktionen teilen sich dieselbe Formatierer-Instanz
console.log(formatDate(new Date()));
console.log(formatDates([new Date(), new Date()]));

Dieses Muster funktioniert gut, wenn Sie Werte im 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 Zahlenformatierer, Listenformatierer und alle anderen Intl-Formatierer.

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

Wiederverwendung von Formatierern in Funktionen

Wenn Sie in verschiedenen Teilen Ihres Codes unterschiedliche Formatierungsoptionen benötigen, können Sie Formatierer innerhalb von Funktionen erstellen und sich auf Closures verlassen, um diese 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();

// Der Formatierer wird einmal erstellt, wenn Sie createDateFormatter aufrufen
// Jeder Aufruf von formatDate verwendet denselben Formatierer wieder
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 Formatierer erstellen möchten, der wiederverwendet wird, aber den Formatierer selbst nicht exponieren wollen.

Wann die Wiederverwendung von Formatierern am wichtigsten ist

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

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

// Ineffizient: erstellt N Formatierer
for (let i = 0; i < 1000; i++) {
  const formatted = new Intl.NumberFormat('en-US').format(i);
  processValue(formatted);
}

// Effizient: erstellt 1 Formatierer
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 Wiederverwendung des Formatierers die Neuerstellung des Formatierers bei jedem Aufruf.

// Ineffizient: erstellt Formatierer bei jedem Aufruf
function formatCurrency(amount) {
  const formatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  });
  return formatter.format(amount);
}

// Effizient: erstellt Formatierer einmal
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, wird der Einrichtungsaufwand für die Erstellung von Formatierern zu einem erheblichen Teil der Gesamtzeit.

// Ineffizient für große Datensätze
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)
  }));
}

// Effizient für große Datensätze
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 Formatierern die Zeit, die für Formatierungsoperationen aufgewendet wird, und verbessert die Reaktionsfähigkeit der Anwendung.

Caching von Formatierern mit verschiedenen Optionen

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

const formatterCache = new Map();

function getNumberFormatter(locale, options) {
  // Erstellen eines Cache-Schlüssels aus Locale und Optionen
  const key = JSON.stringify({ locale, options });

  // Gibt den gecachten Formatierer zurück, falls vorhanden
  if (formatterCache.has(key)) {
    return formatterCache.get(key);
  }

  // Erstellt einen neuen Formatierer und speichert ihn im Cache
  const formatter = new Intl.NumberFormat(locale, options);
  formatterCache.set(key, formatter);
  return formatter;
}

// Erster Aufruf erstellt und speichert den Formatierer
const formatter1 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter1.format(42.50));

// Zweiter Aufruf verwendet den gecachten Formatierer wieder
const formatter2 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter2.format(99.99));

// Unterschiedliche Optionen erstellen und cachen einen neuen Formatierer
const formatter3 = getNumberFormatter('en-US', { style: 'percent' });
console.log(formatter3.format(0.42));

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

Optimierungen moderner Browser

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

Dennoch bleibt die Wiederverwendung von Formatierern eine bewährte Methode. Selbst mit Optimierungen ist das Erstellen eines Formatierers immer noch teurer als der Aufruf der format()-Methode auf einem bestehenden Formatierer. Der Kostenunterschied ist kleiner als früher, existiert aber immer noch.

In leistungsstarkem Code, Code, der in Schleifen ausgeführt wird, und Code, der große Datensätze verarbeitet, bietet die Wiederverwendung von Formatierern weiterhin messbare Vorteile. Die Optimierung der Formatierer-Erstellung eliminiert nicht den Wert der Wiederverwendung von Formatierern.

Wichtige Erkenntnisse

Das Erstellen von Intl-Formatierern ist rechenintensiv, da der Browser Optionen analysieren, Locale-Verhandlung durchführen, Locale-Daten laden und interne Datenstrukturen aufbauen muss. Diese Arbeit findet jedes Mal statt, wenn Sie einen Formatierer erstellen.

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

Die Wiederverwendung von Formatierern ist besonders wichtig in Schleifen, häufig aufgerufenen Funktionen und Code, der große Datensätze verarbeitet. In diesen Szenarien kann der Aufwand für die Erstellung von Formatierern einen erheblichen Teil der gesamten Ausführungszeit ausmachen.

Das einfachste Wiederverwendungsmuster ist die Erstellung von Formatierern auf Modulebene. Für komplexere Szenarien können Sie Closures oder Caching basierend auf Konfigurationsoptionen verwenden.