Una regex è un pattern per trovare testo
Le espressioni regolari descrivono la forma delle stringhe: "quattro cifre", "una parola seguita da una virgola", "qualsiasi cosa che sembri un'email". JavaScript ti offre due modi per crearne una:
La forma letterale (slash intorno al pattern, flag dopo lo slash di chiusura) è quella che userai quasi sempre. Usa new RegExp(...) quando il pattern stesso è dinamico, per esempio quando lo costruisci da un input dell'utente o da una variabile.
La i alla fine è un flag. i significa senza distinzione tra maiuscole e minuscole. Tra poco parliamo meglio dei flag.
test: c'è una corrispondenza?
La domanda più rapida che puoi fare a una regex è "questa stringa contiene una corrispondenza?". Per questo c'è test:
\d significa "una cifra qualsiasi". test restituisce true o false e nient'altro. Se ti serve solo un sì o un no (validare un campo, filtrare un array), test è lo strumento giusto.
match: estrarre il testo trovato
Quando vuoi il testo trovato vero e proprio, usa il metodo match della stringa:
Senza il flag g, match restituisce un array con la prima corrispondenza più alcuni metadati (indice, input). Con g, restituisce tutte le corrispondenze come semplice array di stringhe. Se non trova niente, ottieni null, non un array vuoto, quindi proteggiti di conseguenza:
I flag cambiano il comportamento del pattern
I flag vanno dopo lo slash di chiusura e modificano il modo in cui funziona la ricerca. Quelli che userai di più:
g: globale, trova tutte le corrispondenze invece della sola prima.i: senza distinzione tra maiuscole e minuscole.m: multilinea, così^e$corrispondono all'inizio e alla fine di ogni riga, non solo della stringa.s: dotall, così.corrisponde anche agli a capo.u: consapevole dell'Unicode, necessario per molte emoji e per i pattern non ASCII.
Senza m, ^ si ancora solo all'inizio assoluto della stringa. Con m, si ancora all'inizio di ogni riga, quindi vengono presi sia Roses sia Violets.
Classi di caratteri e quantificatori
I mattoni della maggior parte dei pattern:
\dcifra,\wcarattere di parola (lettera, cifra o trattino basso),\sspazio bianco.[abc]uno tra a, b o c.[^abc]qualsiasi cosa tranne quelli.[a-z]un intervallo..qualsiasi carattere tranne l'a capo.*zero o più,+uno o più,?zero o uno.{3}esattamente tre,{2,5}da due a cinque,{2,}due o più.^inizio,$fine.
Tutto insieme:
\b è un confine di parola: la linea invisibile tra un carattere di parola e uno che non lo è. È utile quando vuoi corrispondenze di "parola intera".
Gruppi di cattura: ricordare parti di una corrispondenza
Le parentesi creano un gruppo che cattura ciò che ha trovato. exec e match restituiscono queste catture insieme alla corrispondenza completa:
L'indice 0 è la corrispondenza completa, e ogni gruppo ha il suo posto dopo. Contare i gruppi per posizione diventa scomodo quando sono più di due, quindi dai loro un nome:
I gruppi con nome si leggono meglio nel punto in cui li usi e sopravvivono se riordini il pattern.
replace: riscrivere il testo trovato
replace riceve un pattern e una sostituzione. La sostituzione può essere una stringa o una funzione:
Senza g viene sostituita solo la prima corrispondenza. Un errore comune è dimenticare il flag e chiedersi perché la seconda email sembra ancora sbagliata.
Le stringhe di sostituzione supportano i riferimenti all'indietro. $1, $2 e così via si riferiscono ai gruppi di cattura; $<name> si riferisce a quelli con nome:
Per qualsiasi cosa vada oltre una semplice sostituzione, passa una funzione. Riceve come argomenti la corrispondenza e le catture:
Il trattino basso è la corrispondenza completa (che non ci serve); n è il primo gruppo di cattura. Questo schema, regex più funzione di sostituzione, risolve la maggior parte delle manipolazioni di testo reali.
matchAll: ogni corrispondenza con i suoi gruppi
String.prototype.matchAll restituisce un iteratore di tutte le corrispondenze con i relativi gruppi di cattura, cosa che il semplice match con il flag g non sa fare:
matchAll richiede il flag g. Senza, ottieni un TypeError. Espandilo in un array ([...text.matchAll(email)]) se ti serve l'accesso diretto agli elementi invece dell'iterazione.
Fare l'escape dei caratteri speciali
Caratteri come . * + ? ( ) [ ] { } | \ ^ $ hanno un significato speciale in una regex. Per cercarli letteralmente, fai l'escape con una barra rovesciata:
La versione senza escape trova examplexcom perché . significa "qualsiasi carattere". Questo tipo di bug è comune, e silenzioso. Se una regex trova troppo, cerca per prima cosa un . senza escape.
Quando costruisci un pattern da un input dell'utente, devi fare l'escape, altrimenti un utente può iniettare sintassi regex:
$& in una stringa di sostituzione è una scorciatoia per "l'intera corrispondenza".
Lookahead e lookbehind
A volte vuoi trovare un testo solo quando è seguito (o preceduto) da qualcosa, senza includere quel qualcosa nella corrispondenza. È quello che fanno i lookaround:
(?= ...)lookahead positivo: "seguito da".(?<= ...)lookbehind positivo: "preceduto da".(?! ...)e(?<! ...)sono le versioni negative.
I lookaround non consumano caratteri, quindi la parte che stai "guardando" resta disponibile per la parte successiva del pattern.
Due parole sulla validazione delle email
Te lo chiederanno spesso: "dammi una regex che validi le email". La risposta onesta è: non farlo. La vera grammatica delle email è intricata, e qualsiasi regex abbastanza corta da essere leggibile sbaglia in qualche direzione. Per validare un form, un pattern pragmatico va benissimo:
Leggilo così: "alcuni caratteri che non sono spazi né @, una @, altri caratteri dello stesso tipo, un punto, altri caratteri dello stesso tipo". Intercetta gli errori di battitura evidenti senza pretendere di rispettare la RFC 5322. Per una verifica reale, invia un'email di conferma.
Errori comuni
Alcune trappole da interiorizzare:
- Dimenticare
gconreplaceomatchAll. Solo la prima corrispondenza, oppure unTypeError. - Lo stato di
lastIndexnelle regex globali. Una regex con il flaggoyricorda dove si era fermata tra una chiamata atest/exece l'altra. Non riusarla su stringhe non correlate: creane una nuova, oppure usamatchAll. - Punti e slash senza escape nei pattern dinamici. Fai sempre l'escape dell'input dell'utente prima di passarlo a
new RegExp(...). - Backtracking catastrofico. Quantificatori annidati come
(a+)+su input patologici possono bloccare una scheda. Se una regex sembra lenta, semplificala.
Prossimo passo: date e orari
Le regex gestiscono la forma del testo; i dati reali però arrivano anche con timestamp da analizzare, formattare e su cui fare calcoli. La prossima pagina parla di Date, Intl.DateTimeFormat e del modello mentale che tiene lontani i bug dei fusi orari.
Domande frequenti
Come si crea una regex in JavaScript?
In due modi. La forma letterale usa gli slash: /hello/i. Il costruttore riceve una stringa: new RegExp('hello', 'i'). Usa la forma letterale quando il pattern è fisso, e il costruttore quando devi costruire il pattern da una variabile a runtime.
Qual è la differenza tra test, match ed exec nelle regex di JavaScript?
regex.test(str) restituisce un booleano: è la scelta più veloce quando ti interessa solo sapere se c'è una corrispondenza. str.match(regex) restituisce un array di corrispondenze (oppure null). regex.exec(str) restituisce una corrispondenza alla volta con i gruppi di cattura e, con il flag g, tiene traccia della posizione tra una chiamata e l'altra tramite lastIndex.
Come sostituisco tutte le occorrenze con una regex in JavaScript?
Usa il flag g: str.replace(/foo/g, 'bar'). Senza g viene sostituita solo la prima corrispondenza. Puoi anche chiamare str.replaceAll(/foo/g, 'bar'), ma replaceAll con una regex come argomento richiede il flag g, altrimenti lancia un errore.
Cosa sono i gruppi di cattura nelle regex di JavaScript?
Le parentesi in un pattern creano gruppi di cattura che ricordano cosa hanno trovato. /(\d{4})-(\d{2})/.exec('2024-11') restituisce un array in cui l'indice 1 è '2024' e l'indice 2 è '11'. Dai loro un nome con (?<year>\d{4}) e accedi al valore con match.groups.year.