Vrei să înveți JavaScript? Ia-mi cartea electronică la jshandbook.com

Introducere în expresii regulate

O expresie regulată (numită și regex pe scurt) este o modalitate rapidă de a lucra cu șiruri de text.

Prin formularea unei expresii regulate cu o sintaxă specială, puteți:

  • căutați text într-un șir
  • înlocuiți șirurile într-un șir
  • și extrage informații dintr-un șir

Aproape fiecare limbaj de programare prezintă o anumită implementare a expresiilor regulate. Există diferențe mici între fiecare implementare, dar conceptele generale se aplică aproape peste tot.

Expresiile regulate datează din anii 1950, când au fost formalizate ca un model conceptual de căutare pentru algoritmi de procesare a șirurilor.

Implementate în instrumentele UNIX precum grep, sed și în editorii de text populari, regexurile au crescut în popularitate. Au fost introduse în limbajul de programare Perl și mai târziu și în multe altele.

JavaScript, împreună cu Perl, este unul dintre limbajele de programare care acceptă expresii regulate integrate direct în limbaj.

Greu dar util

Expresiile obișnuite pot părea o absurditate absolută pentru începători și, de multe ori, și pentru dezvoltatorul profesionist, dacă nu investiți timpul necesar pentru a le înțelege.

Expresiile regulate criptice sunt greu de scris, greu de citit, și greu de întreținut / modificat.

Dar uneori o expresie regulată este singura cale sănătoasă pentru a efectua unele manipulări de șiruri, deci este un instrument foarte valoros în buzunar.

Acest tutorial își propune să vă introducă expresiile regulate JavaScript într-un mod simplu și să vă ofere toate informațiile pentru a citi și a crea expresii regulate.

Regula generală este aceea expresiile regulate simple sunt ușor de citit si scrie, in timp ce expresiile regulate complexe se pot transforma rapid într-o mizerie dacă nu înțelegi profund elementele de bază.

Cum arată o expresie regulată?

În JavaScript, o expresie regulată este un obiect, care poate fi definit în două moduri.

Primul este instanțierea unui obiect RegExp nou folosind constructorul:

const re1 = new RegExp('hey')

Al doilea este utilizarea expresie regulată literală formă:

const re1 = /hey/

Știți că JavaScript are obiecte literale și litere matrice? De asemenea are literali regex.

În exemplul de mai sus, hey se numește model. În forma literală este delimitat de bare oblice, în timp ce cu constructorul de obiecte nu este.

Aceasta este prima diferență importantă dintre cele două forme, dar vom vedea altele mai târziu.

Cum functioneazã?

Expresia regulată pe care am definit-o ca fiind re1 mai sus este una foarte simplă. Se caută în șir hey, fără nicio limitare. Șirul poate conține o mulțime de text și hey la mijloc, iar regexul este satisfăcut. Ar putea conține, de asemenea, doar hey, iar regexul ar fi satisfăcut și.

Este destul de simplu.

Puteți testa regexul folosind RegExp.test(String), care returnează un boolean:

re1.test('hey') //✅ re1.test('blablabla hey blablabla') //✅ re1.test('he') //❌ re1.test('blablabla') //❌

În exemplul de mai sus, am verificat doar dacă "hey" satisface modelul de expresie regulat stocat în re1.

Acesta este cel mai simplu lucru, dar acum știți deja multe concepte despre regexuri.

Ancorare

/hey/

chibrituri hey oriunde era pus în interiorul șirului.

Dacă doriți să potriviți șiruri care start cu hey, folosește ^ operator:

/^hey/.test('hey') //✅ /^hey/.test('bla hey') //❌

Dacă doriți să potriviți șiruri care Sfârșit cu hey, folosește $ operator:

/hey$/.test('hey') //✅ /hey$/.test('bla hey') //✅ /hey$/.test('hey you') //❌

Combinați-le și combinați șirurile care se potrivesc exact hey, și doar acel șir:

/^hey$/.test('hey') //✅

Pentru a potrivi un șir care începe cu un șir și se termină cu altul, puteți utiliza .*, care se potrivește cu orice caracter repetat de 0 sau mai multe ori:

/^hey.*joe$/.test('hey joe') //✅ /^hey.*joe$/.test('heyjoe') //✅ /^hey.*joe$/.test('hey how are you joe') //✅ /^hey.*joe$/.test('hey joe!') //❌

Potriviți elementele din intervale

În loc să potriviți un anumit șir, puteți alege să potriviți orice caracter dintr-un interval, cum ar fi:

/[a-z]/ //a, b, c, ... , x, y, z /[A-Z]/ //A, B, C, ... , X, Y, Z /[a-c]/ //a, b, c /[0-9]/ //0, 1, 2, 3, ... , 8, 9

Aceste regexuri se potrivesc cu șiruri care conțin cel puțin unul dintre caracterele din aceste intervale:

/[a-z]/.test('a') //✅ /[a-z]/.test('1') //❌ /[a-z]/.test('A') //❌ /[a-c]/.test('d') //❌ /[a-c]/.test('dc') //✅

Gamele pot fi combinate:

/[A-Za-z0-9]/
/[A-Za-z0-9]/.test('a') //✅ /[A-Za-z0-9]/.test('1') //✅ /[A-Za-z0-9]/.test('A') //✅

Potrivirea de mai multe ori a unui element de interval

Puteți verifica dacă un șir conține un singur caracter dintr-un interval folosind - char:

/^[A-Za-z0-9]$/ 
/^[A-Za-z0-9]$/.test('A') //✅ /^[A-Za-z0-9]$/.test('Ab') //❌

Negarea unui model

^ caracterul de la începutul unui model îl ancorează la începutul unui șir.

Folosit într-o gamă, ea neagă deci:

/[^A-Za-z0-9]/.test('a') //❌ /[^A-Za-z0-9]/.test('1') //❌ /[^A-Za-z0-9]/.test('A') //❌ /[^A-Za-z0-9]/.test('@') //✅
  • d se potrivește cu orice cifră, echivalent cu [0-9]
  • D se potrivește cu orice caracter care nu este o cifră, echivalent cu [^0-9]
  • w se potrivește cu orice caracter alfanumeric, echivalent cu [A-Za-z0-9]
  • W se potrivește cu orice caracter nealfanumeric, echivalent cu [^A-Za-z0-9]
  • s se potrivește cu orice caracter al spațiului alb: spații, file, linii noi și spații Unicode
  • S se potrivește cu orice personaj care nu este un spațiu alb
  • meciuri nule
  • n se potrivește cu un caracter de linie nouă
  • t se potrivește cu un caracter tab
  • uXXXX corespunde unui caracter unicode cu codul XXXX (necesită u steag)
  • . se potrivește cu orice caracter care nu este o linie nouă (de ex n) (cu excepția cazului în care utilizați s pavilion, explicat mai târziu)
  • [^] se potrivește cu orice caracter, inclusiv cu caractere newline. Este util pe șirurile cu mai multe linii.

Opțiuni de exprimare regulată

Dacă doriți să căutați un șir sau altul, utilizați | operator.

/hey|ho/.test('hey') //✅ /hey|ho/.test('ho') //✅

Cuantificatoare

Spuneți că aveți această regex care verifică dacă un șir are o cifră și nimic altceva:

/^d$/

Poți să folosești ? cuantificator pentru a o face opțională, necesitând astfel zero sau unul:

/^d?$/

dar dacă doriți să potriviți mai multe cifre?

Puteți face acest lucru în 4 moduri, folosind +, *, {n} și {n,m}. Să le privim pe rând.

+

Potriviți unul sau mai multe (> = 1) elemente

/^d+$/ 
/^d+$/.test('12') //✅ /^d+$/.test('14') //✅ /^d+$/.test('144343') //✅ /^d+$/.test('') //❌ /^d+$/.test('1a') //❌

*

Potriviți 0 sau mai multe (> = 0) elemente

/^d+$/ 
/^d*$/.test('12') //✅ /^d*$/.test('14') //✅ /^d*$/.test('144343') //✅ /^d*$/.test('') //✅ /^d*$/.test('1a') //❌

{n}

Potriviți exact n obiecte

/^d{3}$/ 
/^d{3}$/.test('123') //✅ /^d{3}$/.test('12') //❌ /^d{3}$/.test('1234') //❌ /^[A-Za-z0-9]{3}$/.test('Abc') //✅

{n,m}

Meci între n și m ori:

/^d{3,5}$/ 
/^d{3,5}$/.test('123') //✅ /^d{3,5}$/.test('1234') //✅ /^d{3,5}$/.test('12345') //✅ /^d{3,5}$/.test('123456') //❌

m poate fi omis pentru a avea un final deschis, deci aveți cel puțin n obiecte:

/^d{3,}$/ 
/^d{3,}$/.test('12') //❌ /^d{3,}$/.test('123') //✅ /^d{3,}$/.test('12345') //✅ /^d{3,}$/.test('123456789') //✅

Elemente opționale

În urma unui articol cu ? îl face opțional:

/^d{3}w?$/ 
/^d{3}w?$/.test('123') //✅ /^d{3}w?$/.test('123a') //✅ /^d{3}w?$/.test('123ab') //❌

Grupuri

Folosind paranteze, puteți crea grupuri de caractere: (...)

Acest exemplu se potrivește exact cu 3 cifre urmate de unul sau mai multe caractere alfanumerice:

/^(d{3})(w+)$/ 
/^(d{3})(w+)$/.test('123') //❌ /^(d{3})(w+)$/.test('123s') //✅ /^(d{3})(w+)$/.test('123something') //✅ /^(d{3})(w+)$/.test('1234') //✅

Caracterele de repetare puse după închiderea parantezelor unui grup se referă la întregul grup:

/^(d{2})+$/ 
/^(d{2})+$/.test('12') //✅ /^(d{2})+$/.test('123') //❌ /^(d{2})+$/.test('1234') //✅

Captarea grupurilor

Până acum, am văzut cum să testăm șirurile și să verificăm dacă acestea conțin un anumit model.

O caracteristică foarte interesantă a expresiilor regulate este abilitatea de a captează părți ale unui șir, și puneți-le într-o matrice.

Puteți face acest lucru folosind Grupuri, în special Captarea grupurilor.

În mod implicit, un grup este un grup de capturare. Acum, în loc să folosiți RegExp.test(String), care doar returnează un boolean dacă modelul este satisfăcut, îl folosim pe oricare String.match(RegExp) sau RegExp.exec(String).

Acestea sunt exact aceleași și returnează o matrice cu întregul șir asociat în primul articol, apoi fiecare conținut de grup asociat.

Dacă nu există nicio potrivire, se întoarce null:

'123s'.match(/^(d{3})(w+)$/) //Array [ "123s", "123", "s" ] 
/^(d{3})(w+)$/.exec('123s') //Array [ "123s", "123", "s" ] 
'hey'.match(/(hey|ho)/) //Array [ "hey", "hey" ] 
/(hey|ho)/.exec('hey') //Array [ "hey", "hey" ] 
/(hey|ho)/.exec('ha!') //null

Când un grup se potrivește de mai multe ori, doar ultima potrivire este plasată în matricea de rezultate:

'123456789'.match(/(d)+/) //Array [ "123456789", "9" ]

Grupuri opționale

Un grup de capturare poate fi făcut opțional utilizând (...)?. Dacă nu este găsit, slotul matrice rezultat va conține undefined:

/^(d{3})(s)?(w+)$/.exec('123 s') //Array [ "123 s", "123", " ", "s" ] 
/^(d{3})(s)?(w+)$/.exec('123s') //Array [ "123s", "123", undefined, "s" ]

Grupuri potrivite de referință

Fiecărui grup care i se potrivește i se atribuie un număr. $1 se referă la primul, $2 la al doilea și așa mai departe. Acest lucru va fi util când vom vorbi mai târziu despre înlocuirea părților unui șir.

Numit grupuri de capturare

Aceasta este o noutate ES2018 caracteristică.

Un grup poate fi atribuit unui nume, mai degrabă decât să i se atribuie doar un slot în matricea rezultată:

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/ const result = re.exec('2015-01-02') 
// result.groups.year === '2015'; // result.groups.month === '01'; // result.groups.day === '02';
Un ghid simplu si rapid pentru JavaScript

Utilizarea meciului și executarea fără grupuri

Există o diferență între utilizarea match și exec fără grupuri: primul element din matrice nu este întregul șir de potrivire, ci potrivirea directă:

/hey|ho/.exec('hey') // [ "hey" ] 
/(hey).(ho)/.exec('hey ho') // [ "hey ho", "hey", "ho" ]

Grupuri care nu capturează

Deoarece grupurile implicite sunt Grupuri de captare, aveți nevoie de o modalitate de a ignora unele grupuri din matricea rezultată. Acest lucru este posibil folosind Grupuri necapturante, care încep cu a (?:...)

'123s'.match(/^(d{3})(?:s)(w+)$/)//null 
'123 s'.match(/^(d{3})(?:s)(w+)$/) //Array [ "123 s", "123", "s" ]

Steaguri

Puteți utiliza următoarele steaguri pe orice expresie regulată:

  • g: se potrivește cu modelul de mai multe ori
  • i: face ca regex să nu fie sensibil la majuscule
  • m: activează modul multilinie. În acest mod, ^ și $ potriviți începutul și sfârșitul întregului șir. Fără aceasta, cu șiruri de linii multiple, acestea se potrivesc cu începutul și sfârșitul fiecărei linii.
  • u: permite suport pentru unicode (introdus în ES6 / ES2015)
  • s: (nou in ES2018) scurt pentru o singura linie, provoacă . pentru a se potrivi și cu caractere de linie noi.

Steagurile pot fi combinate și sunt adăugate la sfârșitul șirului în litere regex:

/hey/ig.test('HEy') //✅

sau ca al doilea parametru cu constructorii de obiecte RegExp:

new RegExp('hey', 'ig').test('HEy') //✅

Inspectând o regex

Având în vedere o regex, îi puteți inspecta proprietățile:

  • source șirul de model
  • multiline adevărat cu m steag
  • global adevărat cu g steag
  • ignoreCase adevărat cu i steag
  • lastIndex
/^(w{3})$/i.source //"^(\d{3})(\w+)$" /^(w{3})$/i.multiline //false /^(w{3})$/i.lastIndex //0 /^(w{3})$/i.ignoreCase //true /^(w{3})$/i.global //false

Scăpând

Aceste personaje sunt speciale:

  • /
  • [ ]
  • ( )
  • { }
  • ?
  • +
  • *
  • |
  • .
  • ^
  • $

Sunt speciale, deoarece sunt caractere de control care au o semnificație în modelul de expresie regulat. Dacă doriți să le utilizați în interiorul tiparului ca caractere potrivite, trebuie să le scăpați, înaintând o bară inversă:

/^\$/ /^^$/ // /^^$/.test('^') ✅ /^$$/ // /^$$/.test('$') ✅

Limitele șirului

b și B vă permite să inspectați dacă un șir este la începutul sau la sfârșitul unui cuvânt:

  • b se potrivește cu un set de caractere la începutul sau la sfârșitul unui cuvânt
  • B se potrivește cu un set de caractere care nu se află la începutul sau la sfârșitul unui cuvânt

Exemplu:

'I saw a bear'.match(/bbear/) //Array ["bear"] 'I saw a beard'.match(/bbear/) //Array ["bear"] 'I saw a beard'.match(/bbearb/) //null 'cool_bear'.match(/bbearb/) //null

Înlocuiți, folosind expresii regulate

Am văzut deja cum să verificăm dacă un șir conține un model.

De asemenea, am văzut cum să extragem părți ale unui șir într-un tablou, potrivind un model.

Să vedem cum înlocuiți părți ale unui șir pe baza unui tipar.

String obiectul din JavaScript are o metodă replace (), care poate fi utilizată fără expresii regulate pentru a efectua un înlocuire unică pe un șir:

"Hello world!".replace('world', 'dog') //Hello dog! 
"My dog is a good dog!".replace('dog', 'cat') //My cat is a good dog!

Această metodă acceptă, de asemenea, o expresie regulată ca argument:

"Hello world!".replace(/world/, 'dog') //Hello dog!

Folosind g steagul este singura cale pentru a înlocui mai multe apariții într-un șir în JavaScript vaniliat:

"My dog is a good dog!".replace(/dog/g, 'cat') //My cat is a good cat!

Grupurile ne permit să facem lucruri mai fanteziste, cum ar fi deplasarea în jurul unor părți ale unui șir:

"Hello, world!".replace(/(w+), (w+)!/, '$2: $1!!!') // "world: Hello!!!"

În loc să utilizați un șir, puteți utiliza o funcție, pentru a face lucruri chiar mai îndrăznețe. Va primi o serie de argumente precum cel returnat de String.match(RegExp) sau RegExp.exec(String), cu o serie de argumente care depind de numărul de grupuri:

"Hello, world!".replace(/(w+), (w+)!/, (matchedString, first, second) => {   console.log(first);   console.log(second); 
  return `${second.toUpperCase()}: ${first}!!!` }) 
//"WORLD: Hello!!!"

Lăcomia

Se spune că expresiile regulate sunt lacom în mod implicit.

Ce înseamnă?

Luați această regex:

/$(.+)s?/

Se presupune că se extrage o sumă de dolari dintr-un șir:

/$(.+)s?/.exec('This costs $100')[1] //100

dar dacă avem mai multe cuvinte după număr, ne sperie:

/$(.+)s?/.exec('This costs $100 and it is less than $200')[1] //100 and it is less than $200

De ce? Deoarece regexul după semnul $ se potrivește cu orice caracter cu .+, și nu se va opri până când nu ajunge la sfârșitul șirului. Apoi, se termină pentru că s? face ca spațiul final să fie opțional.

Pentru a remedia acest lucru, trebuie să spunem regexului să fie leneș și să efectueze cea mai mică cantitate posibilă de potrivire. Putem face acest lucru folosind ? simbol după cuantificator:

/$(.+?)s/.exec('This costs $100 and it is less than $200')[1] //100

Am eliminat ? după s . În caz contrar, se potrivea doar cu primul număr, deoarece spațiul era opțional

Asa de, ? înseamnă lucruri diferite în funcție de poziția sa, deoarece poate fi atât un cuantificator, cât și un indicator al modului leneș.

Lookaheads: potriviți un șir în funcție de ceea ce îl urmează

Utilizare ?= pentru a se potrivi cu un șir urmat de un anumit șir:

/Roger(?=Waters)/ 
/Roger(?= Waters)/.test('Roger is my dog') //false /Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true

?! efectuează operația inversă, potrivind dacă un șir este nu urmată de un șir specific:

/Roger(?!Waters)/ 
/Roger(?! Waters)/.test('Roger is my dog') //true /Roger(?! Waters)/.test('Roger Waters is a famous musician') //false

Lookbehinds: potriviți un șir în funcție de ceea ce îl precedă

Aceasta este o ES2018 caracteristică.

Lookaheads folosesc ?= simbol. Utilizarea Lookbehinds ?&lt; =.

/(?<=Roger) Waters/ 
/(?<=Roger) Waters/.test('Pink Waters is my dog') //false 
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true

O privire în spate este negată folosind ?&lt;!:

/(?<!Roger) Waters/ 
/(?<!Roger) Waters/.test('Pink Waters is my dog') //true 
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false

Expresii regulate și Unicode

u semnalizatorul este obligatoriu atunci când se lucrează cu șiruri Unicode. În special, acest lucru se aplică atunci când este posibil să aveți nevoie să gestionați caractere în planuri astrale (cele care nu sunt incluse în primele 1600 de caractere Unicode).

Emojis sunt un bun exemplu, dar nu sunt singurele.

Dacă nu adăugați acel steag, această regex simplă care ar trebui să se potrivească cu un caracter nu va funcționa, deoarece pentru JavaScript emoji-ul respectiv este reprezentat intern de 2 caractere (a se vedea Unicode în JavaScript):

/^.$/.test('a') //✅ /^.$/.test('?') //❌ /^.$/u.test('?') //✅  

So, always use the u steag.

Unicode, just like normal characters, handle ranges:

/[a-z]/.test('a') //✅ /[1-9]/.test('1') //✅ /[?-?]/u.test('?') //✅ /[?-?]/u.test('?') //❌  

JavaScript checks the internal code representation, so ? < ? < ? because u1F436 < u1F43A <; u1F98A. Check E completlista moji pentru a obține acele coduri și pentru a afla ordinea (sfat: selectorul Emoji MacOS are câteva emoji-uri într-o ordine mixtă, deci nu contați pe el).

Unicode property escapes

As we saw above, in a regular expression pattern you can use d pentru a se potrivi cu orice cifră, s pentru a se potrivi cu orice personaj care nu este un spațiu alb, w pentru a se potrivi cu orice caracter alfanumeric și așa mai departe.

The Unicode property escapes is an ES2018 feature that introduces a very cool feature, extending this concept to all Unicode characters introducing p{} și negarea acestuia P{}.

Any Unicode character has a set of properties. For example Script determină familia de limbi, ASCII este un boolean care este adevărat pentru caracterele ASCII și așa mai departe. Puteți pune această proprietate în paranteze grafice, iar regexul va verifica dacă aceasta este adevărată:

/^p{ASCII}+$/u.test('abc') //✅ /^p{ASCII}+$/u.test('ABC@') //✅ /^p{ASCII}+$/u.test('ABC?') //❌ 

ASCII_Hex_Digit este o altă proprietate booleană care verifică dacă șirul conține doar cifre hexazecimale valide:

/^p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅ /^p{ASCII_Hex_Digit}+$/u.test('h') //❌

There are many other boolean properties, which you just check by adding their name in the graph parentheses, including Uppercase, Lowercase, White_Space, Alphabetic, Emoji și altele:

/^p{Lowercase}$/u.test('h') //✅ /^p{Uppercase}$/u.test('H') //✅ 
/^p{Emoji}+$/u.test('H') //❌ /^p{Emoji}+$/u.test('??') //✅  

In addition to those binary properties, you can check any of the unicode character properties to match a specific value. In this example, I check if the string is written in the Greek or Latin alphabet:

/^p{Script=Greek}+$/u.test('ελληνικά') //✅ /^p{Script=Latin}+$/u.test('hey') //✅

Read more about all the properties you can use directly on the proposal.

Examples

Supposing a string has only one number you need to extract, /d+/ ar trebui să o facă:

'Test 123123329'.match(/d+/) // Array [ "123123329" ]

Match an email address

A simplistic approach is to check non-space characters before and after the @ semn, folosind S:

/(S+)@(S+).(S+)/ 
/(S+)@(S+).(S+)/.exec('copesc@gmail.com') //["copesc@gmail.com", "copesc", "gmail", "com"]

This is a simplistic example, however, as many invalid emails are still satisfied by this regex.

Capture text between double quotes

Suppose you have a string that contains something in double quotes, and you want to extract that content.

The best way to do so is by using a capturing group, because we know the match starts and ends with ", și îl putem viza cu ușurință, dar dorim, de asemenea, să eliminăm acele citate din rezultatul nostru.

We’ll find what we need in result[1]:

const hello = 'Hello "nice flower"' const result = /"([^']*)"/.exec(hello) //Array [ ""nice flower"", "nice flower" ]

Get the content inside an HTML tag

For example get the content inside a span tag, allowing any number of arguments inside the tag:

/<spanb[^>]*>(.*?)&lt;/span>/ 
/<spanb[^>]*>(.*?)</span>/.exec('test')// null 
/<spanb[^>]*>(.*?)</span>/.exec('<span>test</span>') // ["&lt;span>test</span>", "test"] 
/<spanb[^>]*>(.*?)</span>/.exec('<span class="x">test</span>') // ["<span class="x">test</span>", "test"]

Interested in learning JavaScript? Get my ebook at jshandbook.com