Wie man Zeichenketten unabhängig von Groß- und Kleinschreibung vergleicht
Verwenden Sie locale-bewussten Vergleich, um case-insensitive Matching korrekt über Sprachen hinweg zu handhaben
Einführung
Case-insensitive Zeichenkettenvergleich tritt häufig in Webanwendungen auf. Benutzer geben Suchanfragen in gemischter Schreibweise ein, geben Benutzernamen mit inkonsistenter Großschreibung ein oder füllen Formulare ohne Rücksicht auf Groß- und Kleinschreibung aus. Ihre Anwendung muss diese Eingaben korrekt abgleichen, unabhängig davon, ob Benutzer Großbuchstaben, Kleinbuchstaben oder gemischte Schreibweise eingeben.
Der einfache Ansatz konvertiert beide Zeichenketten in Kleinbuchstaben und vergleicht sie. Dies funktioniert für englischen Text, schlägt aber bei internationalen Anwendungen fehl. Verschiedene Sprachen haben unterschiedliche Regeln für die Konvertierung zwischen Groß- und Kleinbuchstaben. Eine Vergleichsmethode, die für Englisch funktioniert, kann falsche Ergebnisse für Türkisch, Deutsch, Griechisch oder andere Sprachen liefern.
JavaScript bietet die Intl.Collator API, um case-insensitive Vergleiche korrekt über alle Sprachen hinweg zu handhaben. Diese Lektion erklärt, warum einfache Kleinbuchstabenkonvertierung fehlschlägt, wie locale-bewusster Vergleich funktioniert und wann jeder Ansatz zu verwenden ist.
Der naive Ansatz mit toLowerCase
Die Konvertierung beider Zeichenketten in Kleinbuchstaben vor dem Vergleich ist der gängigste Ansatz für case-insensitive Matching:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.toLowerCase() === str2.toLowerCase());
// true
Dieses Muster funktioniert für ASCII-Text und englische Wörter. Der Vergleich behandelt Groß- und Kleinbuchstabenversionen desselben Buchstabens als identisch.
Sie können diesen Ansatz für Fuzzy-Suche verwenden:
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple", "APPLE PIE"]
Der Filter findet alle Elemente, die die Suchanfrage enthalten, unabhängig von der Groß- und Kleinschreibung. Dies bietet das erwartete Verhalten für Benutzer, die Anfragen eingeben, ohne über Großschreibung nachzudenken.
Warum der naive Ansatz bei internationalem Text fehlschlägt
Die toLowerCase() Methode konvertiert Text gemäß Unicode-Regeln, aber diese Regeln funktionieren nicht in allen Sprachen gleich. Das bekannteste Beispiel ist das türkische i-Problem.
Im Englischen wird der Kleinbuchstabe i in den Großbuchstaben I umgewandelt. Im Türkischen gibt es zwei unterschiedliche Buchstaben:
- Der Kleinbuchstabe mit Punkt
iwird in den Großbuchstaben mit Punktİumgewandelt - Der Kleinbuchstabe ohne Punkt
ıwird in den Großbuchstaben ohne PunktIumgewandelt
Dieser Unterschied führt zu Problemen bei der Groß-/Kleinschreibung-unabhängigen Vergleichsoperation:
const word1 = "file";
const word2 = "FILE";
// In English locale (correct)
console.log(word1.toLowerCase() === word2.toLowerCase());
// true
// In Turkish locale (incorrect)
console.log(word1.toLocaleLowerCase("tr") === word2.toLocaleLowerCase("tr"));
// false - "file" becomes "fıle"
Bei der Umwandlung von FILE in Kleinbuchstaben unter Verwendung türkischer Regeln wird das I zu ı (ohne Punkt), wodurch fıle entsteht. Dies stimmt nicht mit file (mit Punkt auf dem i) überein, sodass der Vergleich false zurückgibt, obwohl die Zeichenketten dasselbe Wort darstellen.
Andere Sprachen haben ähnliche Probleme. Das Deutsche hat das Zeichen ß, das in Großbuchstaben zu SS wird. Das Griechische hat mehrere Kleinbuchstabenformen von Sigma (σ und ς), die beide in Großbuchstaben zu Σ werden. Eine einfache Groß-/Kleinschreibungsumwandlung kann diese sprachspezifischen Regeln nicht korrekt handhaben.
Verwendung von Intl.Collator mit base-Sensitivität für Groß-/Kleinschreibung-unabhängigen Vergleich
Die Intl.Collator-API bietet einen sprachabhängigen Zeichenkettenvergleich mit konfigurierbarer Sensitivität. Die Option sensitivity steuert, welche Unterschiede beim Vergleich relevant sind.
Für einen Groß-/Kleinschreibung-unabhängigen Vergleich verwenden Sie sensitivity: "base":
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("Hello", "hello"));
// 0 (strings are equal)
console.log(collator.compare("Hello", "HELLO"));
// 0 (strings are equal)
console.log(collator.compare("Hello", "Héllo"));
// 0 (strings are equal, accents ignored too)
Die base-Sensitivität ignoriert sowohl Groß-/Kleinschreibungs- als auch Akzentunterschiede. Nur die Basisbuchstaben sind relevant. Der Vergleich gibt 0 zurück, wenn die Zeichenketten auf dieser Sensitivitätsebene äquivalent sind.
Dieser Ansatz behandelt das türkische i-Problem korrekt:
const collator = new Intl.Collator("tr", { sensitivity: "base" });
console.log(collator.compare("file", "FILE"));
// 0 (correctly matches)
console.log(collator.compare("file", "FİLE"));
// 0 (correctly matches, even with dotted İ)
Der Collator wendet automatisch die türkischen Case-Folding-Regeln an. Beide Vergleiche erkennen die Zeichenketten als äquivalent, unabhängig davon, welches Großbuchstaben-I in der Eingabe erscheint.
Verwendung von localeCompare mit der Option sensitivity
Die Methode localeCompare() bietet eine alternative Möglichkeit, einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung durchzuführen. Sie akzeptiert die gleichen Optionen wie Intl.Collator:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.localeCompare(str2, "en", { sensitivity: "base" }));
// 0 (strings are equal)
Dies liefert das gleiche Ergebnis wie die Verwendung von Intl.Collator mit base-Sensitivität. Der Vergleich ignoriert Unterschiede in der Groß- und Kleinschreibung und gibt 0 für äquivalente Strings zurück.
Sie können dies beim Filtern von Arrays verwenden:
const query = "apple";
const items = ["Apple", "Banana", "APPLE PIE", "Orange"];
const matches = items.filter(item =>
item.localeCompare(query, "en", { sensitivity: "base" }) === 0 ||
item.toLowerCase().includes(query.toLowerCase())
);
console.log(matches);
// ["Apple"]
Allerdings gibt localeCompare() nur 0 für exakte Übereinstimmungen auf der angegebenen Sensitivitätsstufe zurück. Es unterstützt keine Teilübereinstimmungen wie includes(). Für die Suche nach Teilstrings müssen Sie weiterhin die Konvertierung in Kleinbuchstaben verwenden oder einen ausgefeilteren Suchalgorithmus implementieren.
Wahl zwischen base- und accent-Sensitivität
Die Option sensitivity akzeptiert vier Werte, die verschiedene Aspekte des String-Vergleichs steuern:
Base-Sensitivität
Base-Sensitivität ignoriert sowohl Groß- und Kleinschreibung als auch Akzente:
const collator = new Intl.Collator("en", { sensitivity: "base" });
console.log(collator.compare("cafe", "café"));
// 0 (accents ignored)
console.log(collator.compare("cafe", "Café"));
// 0 (case and accents ignored)
console.log(collator.compare("cafe", "CAFÉ"));
// 0 (case and accents ignored)
Dies bietet die nachsichtigste Übereinstimmung. Benutzer, die keine Akzentzeichen eingeben können oder diese aus Bequemlichkeit weglassen, erhalten dennoch korrekte Übereinstimmungen.
Accent-Sensitivität
Accent-Sensitivität ignoriert die Groß- und Kleinschreibung, berücksichtigt aber Akzente:
const collator = new Intl.Collator("en", { sensitivity: "accent" });
console.log(collator.compare("cafe", "café"));
// -1 (accents matter)
console.log(collator.compare("cafe", "Café"));
// -1 (accents matter, case ignored)
console.log(collator.compare("Café", "CAFÉ"));
// 0 (case ignored, accents match)
Dies behandelt Buchstaben mit und ohne Akzent als unterschiedlich, während die Groß- und Kleinschreibung ignoriert wird. Verwenden Sie dies, wenn Akzentunterschiede signifikant sind, Unterschiede in der Groß- und Kleinschreibung jedoch nicht.
Auswahl der richtigen Sensitivität für Ihren Anwendungsfall
Für die meisten Anforderungen an Vergleiche ohne Berücksichtigung der Groß- und Kleinschreibung bietet base-Sensitivität die beste Benutzererfahrung:
- Suchfunktionalität, bei der Benutzer Abfragen ohne Akzente eingeben
- Benutzernamen-Abgleich, bei dem die Groß- und Kleinschreibung keine Rolle spielen sollte
- Fuzzy-Suche, bei der Sie maximale Flexibilität wünschen
- Formularvalidierung, bei der
Smithundsmithübereinstimmen sollten
Verwenden Sie Akzentsensitivität, wenn:
- Die Sprache die Unterscheidung von Zeichen mit Akzent erfordert
- Ihre Daten sowohl Versionen mit als auch ohne Akzent mit unterschiedlichen Bedeutungen enthalten
- Sie einen Vergleich benötigen, der Groß-/Kleinschreibung ignoriert, aber Akzente berücksichtigt
Durchführung einer Suche ohne Berücksichtigung der Groß-/Kleinschreibung mit includes
Die Intl.Collator-API vergleicht vollständige Zeichenketten, bietet jedoch kein Substring-Matching. Für eine Suche ohne Berücksichtigung der Groß-/Kleinschreibung müssen Sie weiterhin locale-bewussten Vergleich mit anderen Ansätzen kombinieren.
Eine Option ist die Verwendung von toLowerCase() für die Substring-Suche, wobei Sie dessen Einschränkungen für internationalen Text akzeptieren:
function caseInsensitiveIncludes(text, query, locale = "en") {
return text.toLowerCase().includes(query.toLowerCase());
}
const text = "The Quick Brown Fox";
console.log(caseInsensitiveIncludes(text, "quick"));
// true
Für eine anspruchsvollere Suche, die internationalen Text korrekt verarbeitet, müssen Sie durch mögliche Substring-Positionen iterieren und den Collator für jeden Vergleich verwenden:
function localeAwareIncludes(text, query, locale = "en") {
const collator = new Intl.Collator(locale, { sensitivity: "base" });
for (let i = 0; i <= text.length - query.length; i++) {
const substring = text.slice(i, i + query.length);
if (collator.compare(substring, query) === 0) {
return true;
}
}
return false;
}
const text = "The Quick Brown Fox";
console.log(localeAwareIncludes(text, "quick"));
// true
Dieser Ansatz prüft jeden möglichen Substring der korrekten Länge und verwendet locale-bewussten Vergleich für jeden. Er verarbeitet internationalen Text korrekt, hat jedoch eine schlechtere Performance als einfaches includes().
Performance-Überlegungen bei der Verwendung von Intl.Collator
Das Erstellen einer Intl.Collator-Instanz umfasst das Laden von Locale-Daten und die Verarbeitung von Optionen. Wenn Sie mehrere Vergleiche durchführen müssen, erstellen Sie den Collator einmal und verwenden Sie ihn wieder:
// Inefficient: creates collator for every comparison
function badCompare(items, target) {
return items.filter(item =>
new Intl.Collator("en", { sensitivity: "base" }).compare(item, target) === 0
);
}
// Efficient: creates collator once, reuses it
function goodCompare(items, target) {
const collator = new Intl.Collator("en", { sensitivity: "base" });
return items.filter(item =>
collator.compare(item, target) === 0
);
}
Die effiziente Version erstellt den Collator einmal vor dem Filtern. Jeder Vergleich verwendet dieselbe Instanz und vermeidet wiederholten Initialisierungs-Overhead.
Für Anwendungen, die häufige Vergleiche durchführen, erstellen Sie Collator-Instanzen beim Anwendungsstart und exportieren Sie diese zur Verwendung in Ihrer gesamten Codebasis:
// utils/collation.js
export const caseInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "base"
});
export const accentInsensitiveCollator = new Intl.Collator("en", {
sensitivity: "accent"
});
// In your application code
import { caseInsensitiveCollator } from "./utils/collation";
const isMatch = caseInsensitiveCollator.compare(input, expected) === 0;
Dieses Muster maximiert die Performance und gewährleistet konsistentes Vergleichsverhalten in Ihrer gesamten Anwendung.
Wann toLowerCase versus Intl.Collator verwendet werden sollte
Für rein englischsprachige Anwendungen, in denen Sie den Textinhalt kontrollieren und wissen, dass er nur ASCII-Zeichen enthält, liefert toLowerCase() akzeptable Ergebnisse:
// Acceptable for English-only, ASCII-only text
const isMatch = str1.toLowerCase() === str2.toLowerCase();
Dieser Ansatz ist einfach, schnell und den meisten Entwicklern vertraut. Wenn Ihre Anwendung wirklich niemals internationalen Text verarbeitet, bietet die zusätzliche Komplexität des locale-bewussten Vergleichs möglicherweise keinen Mehrwert.
Für internationale Anwendungen oder Anwendungen, bei denen Benutzer Text in beliebigen Sprachen eingeben, verwenden Sie Intl.Collator mit entsprechender Sensitivität:
// Required for international text
const collator = new Intl.Collator(userLocale, { sensitivity: "base" });
const isMatch = collator.compare(str1, str2) === 0;
Dies gewährleistet korrektes Verhalten unabhängig davon, welche Sprache Benutzer sprechen oder eingeben. Die geringen Performance-Kosten der Verwendung von Intl.Collator sind gerechtfertigt, um fehlerhafte Vergleiche zu vermeiden.
Selbst wenn Ihre Anwendung derzeit nur Englisch unterstützt, erleichtert die Verwendung von locale-bewusstem Vergleich von Anfang an die zukünftige Internationalisierung. Das Hinzufügen von Unterstützung für neue Sprachen erfordert keine Änderungen an der Vergleichslogik.
Praktische Anwendungsfälle für Groß-/Kleinschreibung-unabhängigen Vergleich
Groß-/Kleinschreibung-unabhängiger Vergleich kommt in vielen gängigen Szenarien vor:
Benutzername- und E-Mail-Abgleich
Benutzer geben Benutzernamen und E-Mail-Adressen mit inkonsistenter Großschreibung ein:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function findUserByEmail(users, email) {
return users.find(user =>
collator.compare(user.email, email) === 0
);
}
const users = [
{ email: "[email protected]", name: "John" },
{ email: "[email protected]", name: "Jane" }
];
console.log(findUserByEmail(users, "[email protected]"));
// { email: "[email protected]", name: "John" }
Dies findet den Benutzer unabhängig davon, wie sie ihre E-Mail-Adresse großschreiben.
Such-Autovervollständigung
Autovervollständigungsvorschläge müssen teilweise Eingaben mit Groß-/Kleinschreibung-Unabhängigkeit abgleichen:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function getSuggestions(items, query) {
const queryLower = query.toLowerCase();
return items.filter(item =>
item.toLowerCase().startsWith(queryLower)
);
}
const items = ["Apple", "Apricot", "Banana", "Cherry"];
console.log(getSuggestions(items, "ap"));
// ["Apple", "Apricot"]
Dies liefert Vorschläge unabhängig von der Groß-/Kleinschreibung, die Benutzer eingeben.
Tag- und Kategorie-Abgleich
Benutzer weisen Inhalten Tags oder Kategorien ohne konsistente Großschreibung zu:
const collator = new Intl.Collator("en", { sensitivity: "base" });
function hasTag(item, tag) {
return item.tags.some(itemTag =>
collator.compare(itemTag, tag) === 0
);
}
const article = {
title: "My Article",
tags: ["JavaScript", "Tutorial", "Web Development"]
};
console.log(hasTag(article, "javascript"));
// true
Dies gleicht Tags unabhängig von Unterschieden in der Großschreibung ab.