Explora la coincidencia de patrones avanzada en JavaScript usando expresiones regulares. Aprende sobre la sintaxis regex, aplicaciones prácticas y técnicas de optimización.
Coincidencia de Patrones en JavaScript con Expresiones Regulares: Una Guía Completa
Las expresiones regulares (regex) son una herramienta poderosa para la coincidencia de patrones y la manipulación de texto en JavaScript. Permiten a los desarrolladores buscar, validar y transformar cadenas basadas en patrones definidos. Esta guía proporciona una descripción completa de las expresiones regulares en JavaScript, cubriendo la sintaxis, el uso y las técnicas avanzadas.
¿Qué son las Expresiones Regulares?
Una expresión regular es una secuencia de caracteres que define un patrón de búsqueda. Estos patrones se utilizan para coincidir y manipular cadenas. Las expresiones regulares se utilizan ampliamente en la programación para tareas como:
- Validación de Datos: Garantizar que la entrada del usuario se ajuste a formatos específicos (por ejemplo, direcciones de correo electrónico, números de teléfono).
- Extracción de Datos: Recuperar información específica del texto (por ejemplo, extraer fechas, URL o precios).
- Buscar y Reemplazar: Encontrar y reemplazar texto basado en patrones complejos.
- Procesamiento de Texto: Dividir, unir o transformar cadenas basadas en reglas definidas.
Creación de Expresiones Regulares en JavaScript
En JavaScript, las expresiones regulares se pueden crear de dos maneras:
- Usando un Literal de Expresión Regular: Encerrar el patrón entre barras inclinadas (
/). - Usando el Constructor
RegExp: Crear un objetoRegExpcon el patrón como una cadena.
Ejemplo:
// Usando un literal de expresión regular
const regexLiteral = /hello/;
// Usando el constructor RegExp
const regexConstructor = new RegExp("hello");
La elección entre los dos métodos depende de si el patrón se conoce en tiempo de compilación o se genera dinámicamente. Utilice la notación literal cuando el patrón sea fijo y se conozca de antemano. Utilice el constructor cuando el patrón necesite ser construido programáticamente, especialmente al incorporar variables.
Sintaxis Básica de Regex
Las expresiones regulares constan de caracteres que representan el patrón que se va a coincidir. Aquí hay algunos componentes fundamentales de regex:
- Caracteres Literales: Coinciden con los caracteres mismos (por ejemplo,
/a/coincide con el carácter 'a'). - Metacaracteres: Tienen significados especiales (por ejemplo,
.,^,$,*,+,?,[],{},(),\,|). - Clases de Caracteres: Representan conjuntos de caracteres (por ejemplo,
[abc]coincide con 'a', 'b' o 'c'). - Cuantificadores: Especifican cuántas veces debe ocurrir un carácter o grupo (por ejemplo,
*,+,?,{n},{n,},{n,m}). - Anclas: Coinciden con posiciones en la cadena (por ejemplo,
^coincide con el principio,$coincide con el final).
Metacaracteres Comunes:
.(punto): Coincide con cualquier carácter individual excepto la nueva línea.^(caret): Coincide con el principio de la cadena.$(dólar): Coincide con el final de la cadena.*(asterisco): Coincide con cero o más ocurrencias del carácter o grupo precedente.+(más): Coincide con una o más ocurrencias del carácter o grupo precedente.?(signo de interrogación): Coincide con cero o una ocurrencia del carácter o grupo precedente. Utilizado para caracteres opcionales.[](corchetes): Define una clase de carácter, coincidiendo con cualquier carácter individual dentro de los corchetes.{}(llaves): Especifica el número de ocurrencias a coincidir.{n}coincide exactamente n veces,{n,}coincide n o más veces,{n,m}coincide entre n y m veces.()(paréntesis): Agrupa caracteres y captura la subcadena coincidente.\(barra invertida): Escapa los metacaracteres, permitiéndole coincidir con ellos literalmente.|(barra vertical): Actúa como un operador "o", coincidiendo con la expresión antes o después de ella.
Clases de Caracteres:
[abc]: Coincide con cualquiera de los caracteres a, b o c.[^abc]: Coincide con cualquier carácter que *no* sea a, b o c.[a-z]: Coincide con cualquier letra minúscula de a a z.[A-Z]: Coincide con cualquier letra mayúscula de A a Z.[0-9]: Coincide con cualquier dígito de 0 a 9.[a-zA-Z0-9]: Coincide con cualquier carácter alfanumérico.\d: Coincide con cualquier dígito (equivalente a[0-9]).\D: Coincide con cualquier carácter que no sea un dígito (equivalente a[^0-9]).\w: Coincide con cualquier carácter de palabra (alfanumérico más guion bajo; equivalente a[a-zA-Z0-9_]).\W: Coincide con cualquier carácter que no sea de palabra (equivalente a[^a-zA-Z0-9_]).\s: Coincide con cualquier carácter de espacio en blanco (espacio, tabulación, nueva línea, etc.).\S: Coincide con cualquier carácter que no sea de espacio en blanco.
Cuantificadores:
*: Coincide con el elemento precedente cero o más veces. Por ejemplo,a*coincide con "", "a", "aa", "aaa", y así sucesivamente.+: Coincide con el elemento precedente una o más veces. Por ejemplo,a+coincide con "a", "aa", "aaa", pero no con "".?: Coincide con el elemento precedente cero o una vez. Por ejemplo,a?coincide con "" o "a".{n}: Coincide con el elemento precedente exactamente *n* veces. Por ejemplo,a{3}coincide con "aaa".{n,}: Coincide con el elemento precedente *n* o más veces. Por ejemplo,a{2,}coincide con "aa", "aaa", "aaaa", y así sucesivamente.{n,m}: Coincide con el elemento precedente entre *n* y *m* veces (inclusive). Por ejemplo,a{2,4}coincide con "aa", "aaa", o "aaaa".
Anclas:
^: Coincide con el principio de la cadena. Por ejemplo,^Hellocoincide con cadenas que *comienzan* con "Hello".$: Coincide con el final de la cadena. Por ejemplo,World$coincide con cadenas que *terminan* con "World".\b: Coincide con un límite de palabra. Esta es la posición entre un carácter de palabra (\w) y un carácter que no es de palabra (\W) o el principio o el final de la cadena. Por ejemplo,\bword\bcoincide con la palabra completa "word".
Flags:
Los flags de Regex modifican el comportamiento de las expresiones regulares. Se agregan al final del literal de regex o se pasan como un segundo argumento al constructor RegExp.
g(global): Coincide con todas las ocurrencias del patrón, no solo con la primera.i(ignorar mayúsculas y minúsculas): Realiza coincidencias sin distinción entre mayúsculas y minúsculas.m(multilínea): Habilita el modo multilínea, donde^y$coinciden con el principio y el final de cada línea (separadas por\n).s(dotAll): Permite que el punto (.) coincida también con los caracteres de nueva línea.u(unicode): Habilita el soporte completo de Unicode.y(sticky): Coincide solo desde el índice indicado por la propiedadlastIndexde la regex.
Métodos Regex de JavaScript
JavaScript proporciona varios métodos para trabajar con expresiones regulares:
test(): Prueba si una cadena coincide con el patrón. Devuelvetrueofalse.exec(): Ejecuta una búsqueda de una coincidencia en una cadena. Devuelve un array que contiene el texto coincidente y los grupos capturados, onullsi no se encuentra ninguna coincidencia.match(): Devuelve un array que contiene los resultados de hacer coincidir una cadena con una expresión regular. Se comporta de manera diferente con y sin el flagg.search(): Prueba si hay una coincidencia en una cadena. Devuelve el índice de la primera coincidencia, o -1 si no se encuentra ninguna coincidencia.replace(): Reemplaza las ocurrencias de un patrón con una cadena de reemplazo o una función que devuelve la cadena de reemplazo.split(): Divide una cadena en un array de subcadenas basado en una expresión regular.
Ejemplos Usando Métodos Regex:
// test()
const regex = /hello/;
const str = "hello world";
console.log(regex.test(str)); // Output: true
// exec()
const regex2 = /hello (\w+)/;
const str2 = "hello world";
const result = regex2.exec(str2);
console.log(result); // Output: ["hello world", "world", index: 0, input: "hello world", groups: undefined]
// match() con flag 'g'
const regex3 = /\d+/g; // Coincide con uno o más dígitos globalmente
const str3 = "There are 123 apples and 456 oranges.";
const matches = str3.match(regex3);
console.log(matches); // Output: ["123", "456"]
// match() sin flag 'g'
const regex4 = /\d+/;
const str4 = "There are 123 apples and 456 oranges.";
const match = str4.match(regex4);
console.log(match); // Output: ["123", index: 11, input: "There are 123 apples and 456 oranges.", groups: undefined]
// search()
const regex5 = /world/;
const str5 = "hello world";
console.log(str5.search(regex5)); // Output: 6
// replace()
const regex6 = /world/;
const str6 = "hello world";
const newStr = str6.replace(regex6, "JavaScript");
console.log(newStr); // Output: hello JavaScript
// replace() con una función
const regex7 = /(\d+)-(\d+)-(\d+)/;
const str7 = "Today's date is 2023-10-27";
const newStr2 = str7.replace(regex7, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(newStr2); // Output: Today's date is 27/10/2023
// split()
const regex8 = /, /;
const str8 = "apple, banana, cherry";
const arr = str8.split(regex8);
console.log(arr); // Output: ["apple", "banana", "cherry"]
Técnicas Avanzadas de Regex
Grupos de Captura:
Los paréntesis () se utilizan para crear grupos de captura en expresiones regulares. Los grupos capturados le permiten extraer partes específicas del texto coincidente. Los métodos exec() y match() devuelven un array donde el primer elemento es la coincidencia completa, y los elementos subsiguientes son los grupos capturados.
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match[0]); // Output: 2023-10-27 (La coincidencia completa)
console.log(match[1]); // Output: 2023 (El primer grupo capturado - año)
console.log(match[2]); // Output: 10 (El segundo grupo capturado - mes)
console.log(match[3]); // Output: 27 (El tercer grupo capturado - día)
Grupos de Captura Nombrados:
ES2018 introdujo grupos de captura nombrados, que le permiten asignar nombres a los grupos de captura utilizando la sintaxis (?<name>...). Esto hace que el código sea más legible y mantenible.
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match.groups.year); // Output: 2023
console.log(match.groups.month); // Output: 10
console.log(match.groups.day); // Output: 27
Grupos Sin Captura:
Si necesita agrupar partes de una regex sin capturarlas (por ejemplo, para aplicar un cuantificador a un grupo), puede usar un grupo sin captura con la sintaxis (?:...). Esto evita la asignación innecesaria de memoria para los grupos capturados.
const regex = /(?:https?:\/\/)?([\w\.]+)/; // Coincide con una URL pero solo captura el nombre de dominio
const url = "https://www.example.com/path";
const match = regex.exec(url);
console.log(match[1]); // Output: www.example.com
Lookarounds:
Los lookarounds son aserciones de ancho cero que coinciden con una posición en una cadena basada en un patrón que precede (lookbehind) o sigue (lookahead) esa posición, sin incluir el patrón de lookaround en la coincidencia en sí.
- Lookahead Positivo:
(?=...)Coincide si el patrón dentro del lookahead *sigue* la posición actual. - Lookahead Negativo:
(?!...)Coincide si el patrón dentro del lookahead *no* sigue la posición actual. - Lookbehind Positivo:
(?<=...)Coincide si el patrón dentro del lookbehind *precede* la posición actual. - Lookbehind Negativo:
(?<!...)Coincide si el patrón dentro del lookbehind *no* precede la posición actual.
Ejemplo:
// Lookahead Positivo: Obtener el precio solo cuando es seguido por USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // Output: ["100"]
// Lookahead Negativo: Obtener la palabra solo cuando no es seguida por un número
const regex2 = /\b\w+\b(?! \d)/;
const text2 = "apple 123 banana orange 456";
const matches = text2.match(regex2);
console.log(matches); // Output: null because match() only returns the first match without 'g' flag, which isn't what we need.
// to fix it:
const regex3 = /\b\w+\b(?! \d)/g;
const text3 = "apple 123 banana orange 456";
const matches3 = text3.match(regex3);
console.log(matches3); // Output: [ 'banana' ]
// Lookbehind Positivo: Obtener el valor solo cuando es precedido por $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // Output: ["200"]
// Lookbehind Negativo: Obtener la palabra solo cuando no es precedida por la palabra 'not'
const regex5 = /(?<!not )\w+/;
const text5 = "I am not happy, I am content.";
const match5 = text5.match(regex5); //returns first match if matched, not the array
console.log(match5); // Output: ['am', index: 2, input: 'I am not happy, I am content.', groups: undefined]
// to fix it, use g flag and exec(), but be careful since regex.exec saves the index
const regex6 = /(?<!not )\w+/g;
let text6 = "I am not happy, I am content.";
let match6; let matches6=[];
while ((match6 = regex6.exec(text6)) !== null) {
matches6.push(match6[0]);
}
console.log(matches6); // Output: [ 'I', 'am', 'happy', 'I', 'am', 'content' ]
Referencias Inversas:
Las referencias inversas le permiten referirse a grupos capturados previamente dentro de la misma expresión regular. Utilizan la sintaxis \1, \2, etc., donde el número corresponde al número de grupo capturado.
const regex = /([a-z]+) \1/;
const text = "hello hello world";
const match = regex.exec(text);
console.log(match); // Output: ["hello hello", "hello", index: 0, input: "hello hello world", groups: undefined]
Aplicaciones Prácticas de Expresiones Regulares
Validación de Direcciones de Correo Electrónico:
Un caso de uso común para las expresiones regulares es la validación de direcciones de correo electrónico. Si bien una regex de validación de correo electrónico perfecta es extremadamente compleja, aquí hay un ejemplo simplificado:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
console.log(emailRegex.test("test@example.com")); // Output: true
console.log(emailRegex.test("invalid-email")); // Output: false
console.log(emailRegex.test("test@sub.example.co.uk")); // Output: true
Extracción de URLs del Texto:
Puede utilizar expresiones regulares para extraer URLs de un bloque de texto:
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/g;
const text = "Visit our website at https://www.example.com or check out http://blog.example.org.";
const urls = text.match(urlRegex);
console.log(urls); // Output: ["https://www.example.com", "http://blog.example.org"]
Análisis de Datos CSV:
Las expresiones regulares se pueden utilizar para analizar datos CSV (Valores Separados por Comas). Aquí hay un ejemplo de cómo dividir una cadena CSV en un array de valores, manejando campos entre comillas:
const csvString = 'John,Doe,"123, Main St",New York';
const csvRegex = /(?:"([^"]*(?:""[^"]*)*)")|([^,]+)/g; //Corrected CSV regex
let values = [];
let match;
while (match = csvRegex.exec(csvString)) {
values.push(match[1] ? match[1].replace(/""/g, '"') : match[2]);
}
console.log(values); // Output: ["John", "Doe", "123, Main St", "New York"]
Validación de Números de Teléfono Internacionales
Validar números de teléfono internacionales es complejo debido a los diferentes formatos y longitudes. Una solución robusta a menudo implica el uso de una biblioteca, pero una regex simplificada puede proporcionar una validación básica:
const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
console.log(phoneRegex.test("+1 555 123 4567")); // Output: true (Ejemplo de EE. UU.)
console.log(phoneRegex.test("+44 20 7946 0500")); // Output: true (Ejemplo del Reino Unido)
console.log(phoneRegex.test("+81 3 3224 5000")); // Output: true (Ejemplo de Japón)
console.log(phoneRegex.test("123-456-7890")); // Output: false
Validación de la Fortaleza de la Contraseña
Las expresiones regulares son útiles para hacer cumplir las políticas de fortaleza de la contraseña. El ejemplo a continuación verifica la longitud mínima, mayúsculas, minúsculas y un número.
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
console.log(passwordRegex.test("P@ssword123")); // Output: true
console.log(passwordRegex.test("password")); // Output: false (sin mayúsculas ni números)
console.log(passwordRegex.test("Password")); // Output: false (sin números)
console.log(passwordRegex.test("Pass123")); // Output: false (sin minúsculas)
console.log(passwordRegex.test("P@ss1")); // Output: false (menos de 8 caracteres)
Técnicas de Optimización de Regex
Las expresiones regulares pueden ser computacionalmente costosas, especialmente para patrones complejos o entradas grandes. Aquí hay algunas técnicas para optimizar el rendimiento de regex:
- Sea Específico: Evite el uso de patrones demasiado generales que puedan coincidir con más de lo previsto.
- Use Anclas: Ancle la regex al principio o al final de la cadena siempre que sea posible (
^,$). - Evite el Backtracking: Minimice el backtracking utilizando cuantificadores posesivos (por ejemplo,
++en lugar de+) o grupos atómicos ((?>...)) cuando sea apropiado. - Compile Una Vez: Si usa la misma regex varias veces, compílela una vez y reutilice el objeto
RegExp. - Use Clases de Caracteres Sabiamente: Las clases de caracteres (
[]) son generalmente más rápidas que las alternancias (|). - Manténgalo Simple: Evite las regexes demasiado complejas que sean difíciles de entender y mantener. A veces, dividir una tarea compleja en múltiples regexes más simples o usar otras técnicas de manipulación de cadenas puede ser más eficiente.
Errores Comunes de Regex
- Olvidar Escapar Metacaracteres: No escapar caracteres especiales como
.,*,+,?,$,^,(,),[,],{,},|y\cuando desee que coincidan literalmente. - Uso Excesivo de
.(punto): El punto coincide con cualquier carácter (excepto la nueva línea en algunos modos), lo que puede conducir a coincidencias inesperadas si no se usa con cuidado. Sea más específico cuando sea posible utilizando clases de caracteres u otros patrones más restrictivos. - Avaricia: Por defecto, los cuantificadores como
*y+son codiciosos y coincidirán con la mayor cantidad posible. Utilice cuantificadores perezosos (*?,+?) cuando necesite que coincida la cadena más corta posible. - Uso Incorrecto de Anclas: Malinterpretar el comportamiento de
^(principio de la cadena/línea) y$(final de la cadena/línea) puede conducir a coincidencias incorrectas. Recuerde usar el flagm(multilínea) cuando trabaje con cadenas multilínea y desee que^y$coincidan con el principio y el final de cada línea. - No Manejar Casos Límite: No considerar todos los escenarios de entrada posibles y los casos límite puede conducir a errores. Pruebe sus regexes a fondo con una variedad de entradas, incluyendo cadenas vacías, caracteres inválidos y condiciones límite.
- Problemas de Rendimiento: Construir regexes demasiado complejas e ineficientes puede causar problemas de rendimiento, especialmente con entradas grandes. Optimice sus regexes utilizando patrones más específicos, evitando el backtracking innecesario y compilando regexes que se utilizan repetidamente.
- Ignorar la Codificación de Caracteres: No manejar correctamente la codificación de caracteres (especialmente Unicode) puede conducir a resultados inesperados. Utilice el flag
ucuando trabaje con caracteres Unicode para asegurar una coincidencia correcta.
Conclusión
Las expresiones regulares son una herramienta valiosa para la coincidencia de patrones y la manipulación de texto en JavaScript. Dominar la sintaxis y las técnicas de regex le permite resolver eficientemente una amplia gama de problemas, desde la validación de datos hasta el procesamiento complejo de texto. Al comprender los conceptos discutidos en esta guía y practicar con ejemplos del mundo real, puede volverse competente en el uso de expresiones regulares para mejorar sus habilidades de desarrollo de JavaScript.
Recuerde que las expresiones regulares pueden ser complejas, y a menudo es útil probarlas a fondo utilizando probadores de regex en línea como regex101.com o regexr.com. Esto le permite visualizar las coincidencias y depurar cualquier problema de manera efectiva. ¡Feliz codificación!