Regex to wzorzec do dopasowywania tekstu
Wyrażenia regularne opisują kształty stringów: „cztery cyfry”, „słowo, po którym jest przecinek”, „coś, co wygląda jak e-mail”. JavaScript daje ci dwa sposoby na ich utworzenie:
Literał (ukośniki wokół wzorca i flagi po zamykającym ukośniku) to forma, której będziesz używać najczęściej. Po new RegExp(...) sięgaj, gdy sam wzorzec jest dynamiczny, na przykład budowany z danych od użytkownika albo ze zmiennej.
i na końcu to flaga. i oznacza ignorowanie wielkości liter. O flagach za chwilę.
test: czy pasuje?
Najszybsze pytanie, jakie możesz zadać regexowi, brzmi: „czy ten string zawiera dopasowanie?”. Do tego służy test:
\d oznacza „dowolna cyfra”. test zwraca true albo false i nic więcej. Jeśli potrzebujesz tylko odpowiedzi tak lub nie (walidacja pola, filtrowanie tablicy), test jest właściwym narzędziem.
match: wyciąganie dopasowanego tekstu
Gdy chcesz dostać sam dopasowany tekst, użyj metody match stringa:
Bez flagi g metoda match zwraca tablicę z pierwszym dopasowaniem i metadanymi (index, input). Z g zwraca wszystkie dopasowania jako zwykłą tablicę stringów. Jeśli nic nie pasuje, dostajesz null, a nie pustą tablicę, więc zabezpiecz się przed tym:
Flagi zmieniają działanie wzorca
Flagi stoją po zamykającym ukośniku i zmieniają sposób dopasowywania. Te, po które sięgniesz najczęściej:
g: global, znajduje wszystkie dopasowania zamiast tylko pierwszego.i: ignoruje wielkość liter.m: multiline, dzięki czemu^i$pasują do początku i końca każdej linii, a nie tylko całego stringa.s: dotall, dzięki czemu.pasuje też do znaków nowej linii.u: obsługa Unicode, potrzebna przy wielu emoji i wzorcach spoza ASCII.
Bez m znak ^ kotwiczy tylko na samym początku stringa. Z m kotwiczy na początku każdej linii, więc łapane są zarówno Roses, jak i Violets.
Klasy znaków i kwantyfikatory
Elementy, z których składa się większość wzorców:
\dcyfra,\wznak słowa (litera, cyfra, podkreślenie),\sbiały znak.[abc]jeden ze znaków a, b lub c.[^abc]wszystko oprócz nich.[a-z]zakres..dowolny znak oprócz nowej linii.*zero lub więcej,+jeden lub więcej,?zero lub jeden.{3}dokładnie trzy,{2,5}od dwóch do pięciu,{2,}dwa lub więcej.^początek,$koniec.
Wszystko razem:
\b to granica słowa: niewidzialna linia między znakiem słowa a znakiem, który nim nie jest. Przydaje się, gdy chcesz dopasowywać „całe słowa”.
Grupy przechwytujące: zapamiętywanie części dopasowania
Nawiasy tworzą grupę, która przechwytuje to, co dopasowała. exec i match zwracają te przechwycenia razem z całym dopasowaniem:
Indeks 0 to całe dopasowanie, a każda grupa dostaje po nim własne miejsce. Liczenie grup pozycyjnych robi się niewygodne, gdy jest ich więcej niż dwie, więc nadaj im nazwy:
Grupy nazwane czyta się lepiej w miejscu użycia i przetrwają zmianę kolejności we wzorcu.
replace: przepisywanie dopasowanego tekstu
replace przyjmuje wzorzec i zamiennik. Zamiennikiem może być string albo funkcja:
Bez g zamieniane jest tylko pierwsze dopasowanie. Częsty błąd to zapomnienie o fladze i zdziwienie, dlaczego drugi adres e-mail nadal wygląda źle.
Stringi zamiennika obsługują odwołania wsteczne. $1, $2 itd. odnoszą się do grup przechwytujących, a $<name> do grup nazwanych:
Do wszystkiego, co wykracza poza prostą podmianę, przekaż funkcję. Dostaje ona dopasowanie i przechwycenia jako argumenty:
Podkreślenie to całe dopasowanie (którego nie potrzebujemy), a n to pierwsza grupa przechwytująca. Ten wzorzec, regex plus funkcja zamieniająca, załatwia większość prawdziwej obróbki tekstu.
matchAll: każde dopasowanie z grupami
String.prototype.matchAll zwraca iterator wszystkich dopasowań razem z grupami przechwytującymi, czego zwykłe match z flagą g nie potrafi:
matchAll wymaga flagi g. Bez niej dostaniesz TypeError. Rozwiń wynik do tablicy ([...text.matchAll(email)]), jeśli potrzebujesz dostępu do dowolnego elementu, a nie iteracji.
Escapowanie znaków specjalnych
Znaki takie jak . * + ? ( ) [ ] { } | \ ^ $ mają w regexie specjalne znaczenie. Żeby dopasować je dosłownie, poprzedź je ukośnikiem wstecznym:
Wersja bez escapowania pasuje do examplexcom, bo . oznacza „dowolny znak”. Taki błąd jest częsty i do tego cichy. Jeśli regex dopasowuje za dużo, najpierw szukaj nieescapowanej kropki ..
Gdy budujesz wzorzec z danych od użytkownika, musisz je escapować, inaczej użytkownik może wstrzyknąć składnię regex:
$& w stringu zamiennika to skrót od „całe dopasowanie”.
Lookahead i lookbehind
Czasem chcesz dopasować tekst tylko wtedy, gdy coś po nim następuje (albo go poprzedza), ale bez włączania tego czegoś do dopasowania. Do tego służą lookaroundy:
(?= ...)pozytywny lookahead: „po którym następuje”.(?<= ...)pozytywny lookbehind: „który jest poprzedzony”.(?! ...)i(?<! ...)to wersje negatywne.
Lookaroundy nie zużywają znaków, więc część, na którą „patrzysz”, pozostaje dostępna dla dalszej części wzorca.
Kilka słów o walidacji e-maili
To pytanie pojawia się bardzo często: „daj mi regex, który waliduje e-maile”. Szczera odpowiedź brzmi: nie rób tego. Prawdziwa gramatyka adresów e-mail jest zawiła, a każdy regex na tyle krótki, żeby dało się go przeczytać, w którąś stronę się myli. Do walidacji formularza wystarczy pragmatyczny wzorzec:
Czytaj go tak: „kilka znaków, które nie są białymi znakami ani @, potem @, znowu to samo, kropka i znowu to samo”. To łapie oczywiste literówki bez udawania, że egzekwuje RFC 5322. Do prawdziwej weryfikacji wyślij e-mail z potwierdzeniem.
Typowe pułapki
Kilka pułapek, które warto sobie przyswoić:
- Brak
gprzyreplacealbomatchAll. Tylko pierwsze dopasowanie alboTypeError. - Stan
lastIndexw globalnych regexach. Regex z flagąglubypamięta, gdzie skończył, między wywołaniamitest/exec. Nie używaj jednego dla niepowiązanych stringów: utwórz nowy albo użyjmatchAll. - Nieescapowane kropki i ukośniki w dynamicznych wzorcach. Zawsze escapuj dane od użytkownika, zanim wrzucisz je do
new RegExp(...). - Katastrofalny backtracking. Zagnieżdżone kwantyfikatory, takie jak
(a+)+, na złośliwych danych mogą zamrozić kartę. Jeśli regex wydaje się wolny, uprość go.
Dalej: daty i czas
Regex zajmuje się kształtem tekstu, a prawdziwe dane zawierają też znaczniki czasu, które trzeba parsować, formatować i przeliczać. Następna strona omawia Date, Intl.DateTimeFormat i model myślowy, który chroni przed błędami stref czasowych.
Najczęściej zadawane pytania
Jak utworzyć regex w JavaScript?
Na dwa sposoby. Literał używa ukośników: /hello/i. Konstruktor przyjmuje string: new RegExp('hello', 'i'). Używaj literału, gdy wzorzec jest stały, a konstruktora, gdy musisz zbudować wzorzec ze zmiennej w czasie działania programu.
Czym różnią się test, match i exec w regex JavaScript?
regex.test(str) zwraca wartość logiczną i jest najszybsze, gdy interesuje cię tylko to, czy dopasowanie istnieje. str.match(regex) zwraca tablicę dopasowań (albo null). regex.exec(str) zwraca jedno dopasowanie naraz razem z grupami przechwytującymi, a z flagą g śledzi pozycję między wywołaniami przez lastIndex.
Jak zamienić wszystkie wystąpienia za pomocą regex w JavaScript?
Użyj flagi g: str.replace(/foo/g, 'bar'). Bez g zamieniane jest tylko pierwsze dopasowanie. Możesz też wywołać str.replaceAll(/foo/g, 'bar'), ale replaceAll z argumentem regex wymaga flagi g, w przeciwnym razie rzuca błąd.
Czym są grupy przechwytujące w regex JavaScript?
Nawiasy we wzorcu tworzą grupy przechwytujące, które zapamiętują, co dopasowały. /(\d{4})-(\d{2})/.exec('2024-11') zwraca tablicę, w której indeks 1 to '2024', a indeks 2 to '11'. Możesz je nazwać przez (?<year>\d{4}) i odczytać przez match.groups.year.