Menu

Regex w JavaScript: test, match, replace i grupy przechwytujące

Jak działają wyrażenia regularne w JavaScript: tworzenie wzorców, podstawowe metody (test, match, replace), flagi i grupy przechwytujące na prawdziwych przykładach.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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:

  • \d cyfra, \w znak słowa (litera, cyfra, podkreślenie), \s biał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 g przy replace albo matchAll. Tylko pierwsze dopasowanie albo TypeError.
  • Stan lastIndex w globalnych regexach. Regex z flagą g lub y pamięta, gdzie skończył, między wywołaniami test/exec. Nie używaj jednego dla niepowiązanych stringów: utwórz nowy albo użyj matchAll.
  • 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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ