Cómo comparar cadenas ignorando diferencias de mayúsculas y minúsculas
Utiliza comparación sensible a la configuración regional para manejar coincidencias sin distinción de mayúsculas y minúsculas correctamente en todos los idiomas
Introducción
La comparación de cadenas sin distinción de mayúsculas y minúsculas aparece frecuentemente en aplicaciones web. Los usuarios escriben consultas de búsqueda con mayúsculas y minúsculas mezcladas, introducen nombres de usuario con capitalización inconsistente o rellenan formularios sin prestar atención a las mayúsculas. Tu aplicación necesita hacer coincidir estas entradas correctamente independientemente de si los usuarios escriben en mayúsculas, minúsculas o combinadas.
El enfoque directo convierte ambas cadenas a minúsculas y las compara. Esto funciona para texto en inglés pero falla para aplicaciones internacionales. Diferentes idiomas tienen diferentes reglas para convertir entre mayúsculas y minúsculas. Un método de comparación que funciona para inglés puede producir resultados incorrectos para turco, alemán, griego u otros idiomas.
JavaScript proporciona la API Intl.Collator para manejar la comparación sin distinción de mayúsculas y minúsculas correctamente en todos los idiomas. Esta lección explica por qué la conversión simple a minúsculas falla, cómo funciona la comparación sensible a la configuración regional y cuándo usar cada enfoque.
El enfoque ingenuo con toLowerCase
Convertir ambas cadenas a minúsculas antes de la comparación es el enfoque más común para la coincidencia sin distinción de mayúsculas y minúsculas:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.toLowerCase() === str2.toLowerCase());
// true
Este patrón funciona para texto ASCII y palabras en inglés. La comparación trata las versiones en mayúsculas y minúsculas de la misma letra como idénticas.
Puedes usar este enfoque para búsqueda difusa:
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"]
El filtro encuentra todos los elementos que contienen la consulta de búsqueda independientemente de las mayúsculas y minúsculas. Esto proporciona el comportamiento esperado para usuarios que escriben consultas sin pensar en la capitalización.
Por qué el enfoque ingenuo falla para texto internacional
El método toLowerCase() convierte texto según las reglas Unicode, pero estas reglas no funcionan de la misma manera en todos los idiomas. El ejemplo más famoso es el problema de la i turca.
En inglés, la letra minúscula i se convierte a mayúscula I. En turco, existen dos letras distintas:
- La
iminúscula con punto se convierte aİmayúscula con punto - La
ıminúscula sin punto se convierte aImayúscula sin punto
Esta diferencia rompe la comparación sin distinción entre mayúsculas y minúsculas:
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"
Al convertir FILE a minúsculas usando las reglas turcas, la I se convierte en ı (sin punto), produciendo fıle. Esto no coincide con file (con i con punto), por lo que la comparación devuelve false aunque las cadenas representen la misma palabra.
Otros idiomas tienen problemas similares. El alemán tiene el carácter ß que se convierte a mayúscula SS. El griego tiene múltiples formas minúsculas de sigma (σ y ς) que ambas se convierten a mayúscula Σ. La conversión simple de mayúsculas y minúsculas no puede manejar correctamente estas reglas específicas del idioma.
Usar Intl.Collator con sensibilidad base para comparación sin distinción entre mayúsculas y minúsculas
La API Intl.Collator proporciona comparación de cadenas consciente del idioma con sensibilidad configurable. La opción sensitivity controla qué diferencias importan durante la comparación.
Para comparación sin distinción entre mayúsculas y minúsculas, usa 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)
La sensibilidad base ignora tanto las diferencias de mayúsculas y minúsculas como de acentos. Solo importan las letras base. La comparación devuelve 0 cuando las cadenas son equivalentes en este nivel de sensibilidad.
Este enfoque maneja correctamente el problema de la i turca:
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 İ)
El collator aplica automáticamente las reglas de plegado de mayúsculas y minúsculas del turco. Ambas comparaciones reconocen las cadenas como equivalentes, independientemente de qué I mayúscula aparezca en la entrada.
Usar localeCompare con opción de sensibilidad
El método localeCompare() proporciona una forma alternativa de realizar comparación sin distinción entre mayúsculas y minúsculas. Acepta las mismas opciones que Intl.Collator:
const str1 = "Hello";
const str2 = "HELLO";
console.log(str1.localeCompare(str2, "en", { sensitivity: "base" }));
// 0 (strings are equal)
Esto produce el mismo resultado que usar Intl.Collator con sensibilidad base. La comparación ignora las diferencias de mayúsculas y minúsculas y devuelve 0 para cadenas equivalentes.
Puedes usar esto en el filtrado de arrays:
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"]
Sin embargo, localeCompare() solo devuelve 0 para coincidencias exactas en el nivel de sensibilidad especificado. No admite coincidencias parciales como includes(). Para búsqueda de subcadenas, aún necesitas usar conversión a minúsculas o implementar un algoritmo de búsqueda más sofisticado.
Elegir entre sensibilidad base y de acentos
La opción sensitivity acepta cuatro valores que controlan diferentes aspectos de la comparación de cadenas:
Sensibilidad base
La sensibilidad base ignora tanto mayúsculas y minúsculas como acentos:
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)
Esto proporciona la coincidencia más permisiva. Los usuarios que no pueden escribir caracteres acentuados o que los omiten por conveniencia aún obtienen coincidencias correctas.
Sensibilidad de acentos
La sensibilidad de acentos ignora mayúsculas y minúsculas pero considera los acentos:
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)
Esto trata las letras acentuadas y no acentuadas como diferentes mientras ignora las mayúsculas y minúsculas. Usa esto cuando las diferencias de acentos son significativas pero las diferencias de mayúsculas y minúsculas no lo son.
Elegir la sensibilidad adecuada para tu caso de uso
Para la mayoría de las necesidades de comparación sin distinción entre mayúsculas y minúsculas, la sensibilidad base proporciona la mejor experiencia de usuario:
- Funcionalidad de búsqueda donde los usuarios escriben consultas sin acentos
- Coincidencia de nombres de usuario donde las mayúsculas y minúsculas no deberían importar
- Búsqueda difusa donde deseas máxima flexibilidad
- Validación de formularios donde
Smithysmithdeberían coincidir
Usa sensibilidad de acentos cuando:
- El idioma requiere distinguir caracteres acentuados
- Tus datos contienen versiones acentuadas y no acentuadas con diferentes significados
- Necesitas comparación sin distinción de mayúsculas y minúsculas pero con reconocimiento de acentos
Realizar búsqueda sin distinción entre mayúsculas y minúsculas con includes
La API Intl.Collator compara cadenas completas pero no proporciona coincidencia de subcadenas. Para búsqueda sin distinción entre mayúsculas y minúsculas, aún necesitas combinar la comparación con reconocimiento de idioma con otros enfoques.
Una opción es usar toLowerCase() para la búsqueda de subcadenas pero aceptar sus limitaciones para texto internacional:
function caseInsensitiveIncludes(text, query, locale = "en") {
return text.toLowerCase().includes(query.toLowerCase());
}
const text = "The Quick Brown Fox";
console.log(caseInsensitiveIncludes(text, "quick"));
// true
Para búsquedas más sofisticadas que manejen texto internacional correctamente, necesitas iterar a través de las posibles posiciones de subcadenas y usar el collator para cada comparación:
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
Este enfoque verifica cada posible subcadena de la longitud correcta y usa comparación consciente de la configuración regional para cada una. Maneja texto internacional correctamente pero tiene peor rendimiento que el simple includes().
Consideraciones de rendimiento al usar Intl.Collator
Crear una instancia de Intl.Collator implica cargar datos de configuración regional y procesar opciones. Cuando necesitas realizar múltiples comparaciones, crea el collator una vez y reutilízalo:
// 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
);
}
La versión eficiente crea el collator una vez antes de filtrar. Cada comparación usa la misma instancia, evitando la sobrecarga de inicialización repetida.
Para aplicaciones que realizan comparaciones frecuentes, crea instancias de collator al inicio de la aplicación y expórtalas para usarlas en toda tu base de código:
// 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;
Este patrón maximiza el rendimiento y mantiene un comportamiento de comparación consistente en toda tu aplicación.
Cuándo usar toLowerCase versus Intl.Collator
Para aplicaciones solo en inglés donde controlas el contenido del texto y sabes que contiene solo caracteres ASCII, toLowerCase() proporciona resultados aceptables:
// Acceptable for English-only, ASCII-only text
const isMatch = str1.toLowerCase() === str2.toLowerCase();
Este enfoque es simple, rápido y familiar para la mayoría de los desarrolladores. Si tu aplicación realmente nunca maneja texto internacional, la complejidad añadida de la comparación consciente de la configuración regional puede no proporcionar valor.
Para aplicaciones internacionales o aplicaciones donde los usuarios ingresan texto en cualquier idioma, usa Intl.Collator con la sensibilidad apropiada:
// Required for international text
const collator = new Intl.Collator(userLocale, { sensitivity: "base" });
const isMatch = collator.compare(str1, str2) === 0;
Esto asegura un comportamiento correcto independientemente del idioma que los usuarios hablen o escriban. El pequeño costo de rendimiento de usar Intl.Collator vale la pena para evitar comparaciones incorrectas.
Incluso si tu aplicación actualmente solo soporta inglés, usar comparación consciente de la configuración regional desde el principio facilita la internacionalización futura. Agregar soporte para nuevos idiomas no requiere cambios en la lógica de comparación.
Casos de uso prácticos para la comparación sin distinción de mayúsculas y minúsculas
La comparación sin distinción de mayúsculas y minúsculas aparece en muchos escenarios comunes:
Coincidencia de nombres de usuario y correos electrónicos
Los usuarios escriben nombres de usuario y direcciones de correo electrónico con mayúsculas inconsistentes:
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" }
Esto encuentra al usuario independientemente de cómo escriba su dirección de correo electrónico con mayúsculas.
Autocompletado de búsqueda
Las sugerencias de autocompletado necesitan coincidir con la entrada parcial sin distinción de mayúsculas y minúsculas:
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"]
Esto proporciona sugerencias independientemente de las mayúsculas que escriban los usuarios.
Coincidencia de etiquetas y categorías
Los usuarios asignan etiquetas o categorías al contenido sin mayúsculas consistentes:
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
Esto coincide con las etiquetas independientemente de las diferencias de mayúsculas.