Una regex es un patrón para buscar texto
Las expresiones regulares en JavaScript describen formas de cadenas: "cuatro dígitos", "una palabra seguida de una coma" o "algo que parezca un email". JavaScript te ofrece dos formas de crearlas:
La forma literal —con barras alrededor del patrón y las flags después de la barra de cierre— es la que vas a usar la mayoría de las veces. Recurre a new RegExp(...) solo cuando el patrón es dinámico, por ejemplo si lo construyes a partir de una variable o de lo que escribe el usuario.
Esa i al final es una flag (bandera). La i indica que no distinga entre mayúsculas y minúsculas. Ya hablaremos de las flags en detalle más adelante.
test: ¿hay coincidencia o no?
Lo más rápido que le puedes preguntar a una expresión regular es "¿este texto contiene alguna coincidencia?". Para eso está test:
\d significa "cualquier dígito". test devuelve true o false, nada más. Cuando lo único que necesitas es un sí o un no —validar un campo, filtrar un array—, test es la herramienta indicada.
match: extraer el texto coincidente
Cuando lo que te interesa es el texto que coincidió, usa el método match del string:
Sin la flag g, match devuelve un array con la primera coincidencia más algo de metadata (el index, el input). Con g, te devuelve todas las coincidencias como un array plano de strings. Y ojo: si no hay coincidencias, el resultado es null, no un array vacío, así que conviene protegerse ante ese caso:
Las flags cambian el comportamiento del patrón
Las flags (o banderas) van después de la barra final y modifican cómo se hace la coincidencia. Estas son las que vas a usar casi siempre:
g— global, busca todas las coincidencias en lugar de quedarse con la primera.i— ignora mayúsculas y minúsculas.m— multilínea, hace que^y$coincidan con el inicio y fin de cada línea, no solo con el inicio y fin de la cadena.s— dotall, permite que.también coincida con saltos de línea.u— soporte Unicode, imprescindible para muchos emojis y patrones que no sean ASCII.
Sin la flag m, ^ solo hace match con el inicio absoluto de la cadena. Con m activada, el anclaje se aplica al inicio de cada línea, así que tanto Roses como Violets quedan capturados.
Clases de caracteres y cuantificadores en regex
Los ladrillos con los que se construyen la mayoría de las expresiones regulares en JavaScript:
\ddígito,\wcarácter de palabra (letra, dígito o guion bajo),\sespacio en blanco.[abc]uno de a, b o c.[^abc]cualquier cosa menos esos.[a-z]un rango..cualquier carácter excepto el salto de línea.*cero o más,+uno o más,?cero o uno.{3}exactamente tres,{2,5}entre dos y cinco,{2,}dos o más.^inicio,$final.
Combinándolo todo:
\b representa un límite de palabra: esa línea invisible que separa un carácter de palabra de uno que no lo es. Viene genial cuando necesitas hacer match con "palabras completas".
Grupos de captura: guardar partes del match
Los paréntesis crean un grupo que captura lo que haya coincidido dentro. Tanto exec como match devuelven esas capturas junto con el match completo:
El índice 0 contiene la coincidencia completa, y cada grupo queda en su propia posición después de ese. Andar contando grupos por posición se vuelve incómodo en cuanto tienes más de dos, así que mejor dales un nombre:
Los grupos con nombre se leen mucho mejor en el lugar donde los usas y, además, aguantan sin romperse si reordenas el patrón.
replace: reescribir el texto coincidente
replace recibe un patrón y un reemplazo. El reemplazo puede ser un string o una función:
Sin la flag g, solo se reemplaza la primera coincidencia. Un error típico es olvidarse de esa flag y quedarse preguntándote por qué el segundo email sigue mal.
En la cadena de reemplazo puedes usar retrorreferencias. $1, $2, etc. apuntan a los grupos de captura, y $<nombre> hace lo propio con los grupos nombrados:
Cuando necesites algo más que un simple reemplazo, pásale una función. Esta recibe como argumentos la coincidencia y los grupos capturados:
El guion bajo representa la coincidencia completa (que no nos interesa); n es el primer grupo de captura. Este patrón —regex combinado con una función de reemplazo— resuelve la mayoría de los casos reales de manipulación de texto.
matchAll: todas las coincidencias con sus grupos de captura
String.prototype.matchAll devuelve un iterador con todas las coincidencias junto con sus grupos de captura, algo que match con la flag g no puede hacer:
matchAll necesita el flag g. Sin él, te va a lanzar un TypeError. Si quieres acceso aleatorio en lugar de iterar, expándelo a un array ([...text.matchAll(email)]).
Escapar caracteres especiales
Ciertos caracteres como . * + ? ( ) [ ] { } | \ ^ $ tienen un significado especial dentro de una regex. Para hacer match con ellos de forma literal, hay que escaparlos con una barra invertida:
La versión sin escapar también hace match con examplexcom, porque el . significa "cualquier carácter". Este tipo de bug es muy común, y encima es silencioso. Si una expresión regular está capturando más de lo que debería, lo primero que tienes que revisar es si hay algún . sin escapar.
Cuando armas un patrón a partir de input del usuario, tienes que escaparlo, o cualquiera podría inyectar sintaxis de regex:
$& en una cadena de reemplazo es la forma corta de decir "toda la coincidencia".
Lookahead y lookbehind
A veces te interesa buscar un texto solo cuando va seguido (o precedido) de otra cosa, pero sin que esa otra cosa forme parte de la coincidencia. Para eso están los lookarounds:
(?= ...)lookahead positivo: "seguido de".(?<= ...)lookbehind positivo: "precedido de".(?! ...)y(?<! ...)son las versiones negativas.
Los lookarounds no consumen caracteres, así que la parte que estás "mirando" sigue disponible para el resto del patrón.
Sobre validar emails con regex
Esta pregunta aparece todo el tiempo: "dame una regex que valide correos electrónicos". La respuesta honesta es: no lo hagas. La gramática real de un email es un lío, y cualquier expresión regular lo bastante corta como para leerse va a fallar por algún lado. Para validar un formulario, con un patrón pragmático basta:
Léelo así: "algunos caracteres que no son espacios ni @, una @, más de lo mismo, un punto, más de lo mismo". Con eso cazas los typos evidentes sin pretender cumplir el RFC 5322. Para verificar de verdad, manda un correo de confirmación.
Errores comunes al usar regex en JavaScript
Hay varias trampas que conviene grabarse a fuego:
- Olvidar la flag
gconreplaceomatchAll. O solo reemplazas la primera coincidencia, o te comes unTypeError. - El
lastIndexcon estado en regex globales. Una expresión regular con la flaggoyrecuerda por dónde iba entre llamadas atest/exec. No la reutilices entre cadenas que no tienen nada que ver: crea una nueva, o tira dematchAll. - Puntos y barras sin escapar en patrones dinámicos. Escapa siempre la entrada del usuario antes de meterla en un
new RegExp(...). - Backtracking catastrófico. Cuantificadores anidados como
(a+)+con una entrada maliciosa pueden congelarte la pestaña. Si una regex te resulta lenta, simplifícala.
Siguiente paso: fechas y horas
Las expresiones regulares se encargan de la forma del texto, pero los datos reales también traen timestamps que hay que parsear, formatear y operar. En la siguiente página veremos Date, Intl.DateTimeFormat y el modelo mental que te ahorra los típicos bugs de zonas horarias.
Preguntas frecuentes
¿Cómo se crea una expresión regular en JavaScript?
Tienes dos opciones. La forma literal va entre barras: /hola/i. El constructor recibe un string: new RegExp('hola', 'i'). Usa la literal cuando el patrón es fijo y el constructor cuando necesites construirlo a partir de una variable en tiempo de ejecución.
¿Qué diferencia hay entre test, match y exec?
regex.test(str) devuelve un booleano — es lo más rápido cuando solo te importa saber si hay coincidencia. str.match(regex) te devuelve un array con las coincidencias (o null). regex.exec(str) devuelve una coincidencia cada vez, con sus grupos de captura, y con el flag g va avanzando entre llamadas gracias a lastIndex.
¿Cómo reemplazo todas las coincidencias con regex en JavaScript?
Añade el flag g: str.replace(/foo/g, 'bar'). Sin g solo se sustituye la primera coincidencia. También puedes usar str.replaceAll(/foo/g, 'bar'), pero ojo: si le pasas una regex a replaceAll, el flag g es obligatorio, si no lanza un error.
¿Qué son los grupos de captura en regex?
Los paréntesis dentro de un patrón crean grupos de captura que recuerdan lo que coincidió. /(\d{4})-(\d{2})/.exec('2024-11') te devuelve un array donde la posición 1 es '2024' y la 2 es '11'. También puedes nombrarlos con (?<year>\d{4}) y acceder a ellos vía match.groups.year.