Wyrażenia regularne w JavaScript pozwalają szybko sprawdzać, czy tekst pasuje do wzorca, wycinać z niego potrzebne fragmenty i zamieniać powtarzalne schematy jednym ruchem. W praktyce przydają się przy walidacji formularzy, porządkowaniu danych, pracy ze slugami, logami i importem tekstu z zewnętrznych źródeł. Właśnie dlatego wyrażenia regularne JS warto znać od strony praktycznej, nie tylko definicyjnej.
Najważniejsze rzeczy do zapamiętania na start
- Regex w JavaScript to wzorzec do dopasowywania tekstu, a nie osobny język magicznych skrótów.
- Stałe wzorce najwygodniej zapisywać literałem `/.../`, a dynamiczne przez `new RegExp()`.
- Flagi, takie jak `g`, `i`, `m`, `s`, `u`, `v`, `y` i `d`, realnie zmieniają zachowanie dopasowania.
- Do sprawdzenia, czy coś pasuje, zwykle wystarcza `test()`, a gdy potrzebuję szczegółów, sięgam po `exec()` albo `matchAll()`.
- Najczęstsze problemy to brak escapowania znaków specjalnych, zbyt zachłanne dopasowanie i ignorowanie Unicode.
Czym są regexy i kiedy naprawdę się przydają
Regex to po prostu wzorzec, który opisuje, jak ma wyglądać fragment tekstu. Ja traktuję go jak precyzyjne narzędzie do powtarzalnych danych: kodów pocztowych, identyfikatorów, dat, numerów zamówień, nazw plików, prostych walidacji albo wyciągania konkretnej części zdania. Jeśli tekst ma wyraźny schemat, regex zwykle oszczędza sporo ręcznego kodu.
Nie używam go jednak do wszystkiego. Gdy logika zależy od wielu reguł biznesowych, warunków i wyjątków, czytelniejszy bywa zwykły kod z kilkoma prostymi krokami. To ważne rozróżnienie, bo źle dobrany wzorzec potrafi wyglądać sprytnie, ale potem trudno go utrzymać albo rozszerzyć.
- Do szybkiej walidacji prostego formatu, na przykład `00-001` lub `+48 123 456 789`.
- Do wyszukiwania fragmentów, na przykład numerów zgłoszeń w logach.
- Do czyszczenia tekstu, na przykład wielokrotnych spacji, zbędnych separatorów albo nowych linii.
- Do zamiany części danych, na przykład zmiany formatu daty lub nazwy pliku.
Jeśli wzorzec już się klaruje, czas zapisać go tak, żeby dało się go później łatwo odczytać i bezpiecznie używać w kodzie.

Jak zbudować wzorzec bez zgadywania
W JavaScript mam dwie podstawowe drogi tworzenia regexa: zapis literałowy i konstruktor `RegExp`. W praktyce wybór zależy od tego, czy wzorzec jest stały, czy budowany dynamicznie. To ma znaczenie nie tylko dla wygody, ale też dla czytelności i sposobu, w jaki trzeba escapować znaki specjalne.
Literał czy konstruktor
Gdy wzorzec jest stały, najczytelniejszy jest zapis literałowy. Jeśli natomiast składam wzorzec z danych zewnętrznych, używam konstruktora. Przy dynamicznych danych trzeba uważać szczególnie na ucieczkę znaków, bo zwykły tekst nie jest jeszcze bezpiecznym regexem.
const postalCode = /^\d{2}-\d{3}$/;
const text = "00-001";
// gdy wzorzec powstaje z danych:
const prefix = "ORD";
const orderPattern = new RegExp("^" + prefix + "-\\d+$");Literał jest kompilowany od razu, gdy kod się ładuje. Konstruktor działa w czasie wykonania, więc nadaje się wtedy, gdy wzorzec powstaje dopiero w trakcie działania aplikacji, na przykład po wyborze filtra w panelu albo po wejściu użytkownika.
Flagi, które robią różnicę
Flagi często zmieniają wynik bardziej niż sam wzorzec. Ja traktuję je jak część regexa, a nie dodatek. Bez nich ten sam zapis może działać zupełnie inaczej.
| Flaga | Co robi | Kiedy się przydaje |
|---|---|---|
d |
Zwraca indeksy dopasowań | Gdy chcę wiedzieć, gdzie dokładnie leży fragment w tekście |
g |
Szuka globalnie, czyli wszystkich dopasowań | Przy wyszukiwaniu wielu wyników albo masowej zamianie |
i |
Ignoruje wielkość liter | Gdy użytkownik może wpisać tekst w dowolnym zapisie |
m |
^ i $ działają dla każdej linii |
Przy pracy z wieloma liniami tekstu lub logami |
s |
Kropka dopasowuje też znak nowej linii | Gdy wzorzec ma objąć blok tekstu, a nie tylko jedną linię |
u |
Włącza obsługę Unicode | Gdy pracuję z diakrytykami, emoji i znakami spoza ASCII |
v |
Rozszerza tryb Unicode o dodatkowe możliwości | Gdy potrzebuję nowocześniejszej pracy z zestawami Unicode |
y |
Dopasowuje od bieżącej pozycji | Przy parsowaniu tekstu krok po kroku |
W praktyce najczęściej używam `g`, `i`, `m`, `u` i coraz częściej `v`. Jeśli tekst trafia od użytkownika, a zawiera znaki narodowe, emoji albo nietypowe separatory, Unicode przestaje być dodatkiem, a staje się koniecznością.
Escaping i dynamiczne dane
Jeżeli do regexa dokładam zwykły tekst, najpierw muszę go zabezpieczyć. W przeciwnym razie znak `.` nie oznacza już kropki, tylko dowolny znak, a `+` zaczyna działać jak operator powtórzeń. To jeden z najprostszych sposobów na ukryty błąd w kodzie.
function escapeRegExp(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}Takie escapowanie jest szczególnie ważne wtedy, gdy wzorzec powstaje z konfiguracji, wyszukiwarki w panelu admina albo pola formularza. Bez tego regex zaczyna interpretować dane wejściowe jako instrukcję, a nie zwykły tekst.
Gdy składnia jest już pod kontrolą, można przejść do samych elementów wzorca i zobaczyć, które z nich rzeczywiście robią największą różnicę.
Elementy wzorca, które używam najczęściej
Nie ma potrzeby uczyć się całej składni naraz. W codziennej pracy wystarcza kilka klocków, które dają większość efektu: klasy znaków, kwantyfikatory, kotwice, grupy i alternatywy. Gdy dobrze je rozumiem, potrafię zbudować wzorzec szybciej niż przez losowe testowanie kombinacji.
| Element | Co robi | Przykład |
|---|---|---|
[abc] |
Dopasowuje jeden z kilku znaków | /[aeiou]/ |
\d |
Dopasowuje cyfrę | /\d{2}-\d{3}/ |
\w |
Dopasowuje znak słowa | /\w+/ |
^ i $
|
Zakotwiczają początek i koniec tekstu | /^\d+$/ |
*, +, ?, {n,m}
|
Określają liczbę powtórzeń | /\d{2,4}/ |
(...) i (?:...)
|
Tworzą grupę przechwytującą albo nieprzechwytującą | /(?:http|https):\/\// |
a|b |
Oznacza alternatywę | /(jpg|png|webp)$/ |
Najwięcej problemów zaczyna się od kwantyfikatorów. `*` i `+` są zachłanne, czyli próbują połknąć jak najwięcej tekstu. To bywa wygodne, ale przy zbyt szerokim wzorcu szybko robi się z tego pułapka. Na przykład w HTML-u regex może znaleźć za dużo, jeśli próbuję nim wycinać tagi bez dodatkowych ograniczeń. Do prostych, kontrolowanych przypadków jeszcze się nadaje, ale pełnego parsowania HTML-a nie robię regexem.
Chciwe i leniwe dopasowanie
Różnica między dopasowaniem chciwym i leniwym jest subtelna tylko na początku. Chciwe dopasowanie bierze najdłuższy możliwy fragment, a leniwe kończy się jak najwcześniej. W praktyce to często decyduje o tym, czy regex zwróci jeden sensowny wynik, czy zbyt duży kawałek tekstu.
const text = "pierwszy i drugi";
text.match(/.*<\/b>/); // za dużo, bo dopasuje od pierwszego do ostatniego tagu
text.match(/.*?<\/b>/); // krócej, bo zatrzymuje się na pierwszym możliwym zamknięciuTo dobry przykład tego, że jeden znak potrafi zmienić całe zachowanie wzorca. Gdy widzę `.*`, od razu pytam siebie, czy naprawdę chcę aż tak szerokie dopasowanie.
Grupy i kontekst
Grupy przechwytujące przydają się wtedy, gdy chcę później użyć fragmentu dopasowania w zamianie albo w logice aplikacji. Z kolei grupy nieprzechwytujące są wygodne, gdy potrzebuję tylko struktury wzorca, ale nie chcę zapełniać wyniku dodatkowymi wartościami. To drobny szczegół, który robi porządek w kodzie.
Jeszcze bardziej przydatne bywają lookahead i lookbehind, czyli sprawdzanie kontekstu bez włączania go do samego wyniku. Na przykład `\d+(?= zł)` dopasuje liczbę przed `zł`, ale samo `zł` zostawi poza wynikiem. Taki zabieg jest prosty, a często dużo czytelniejszy niż późniejsze obcinanie tekstu ręcznie.
Gdy wzorzec już działa, najważniejsze staje się to, jakiej metody użyć do jego uruchomienia. To właśnie tam najczęściej pojawiają się praktyczne różnice.
Jakie metody wywoływać w praktyce
W JavaScript regex żyje razem z metodami `RegExp` i `String`. Nie wybieram ich przypadkowo, bo każda zwraca coś innego: boolean, indeks, tablicę albo szczegółowe dane o dopasowaniu. To właśnie metoda decyduje, czy rozwiązanie będzie eleganckie, czy będzie wymagało dodatkowego ręcznego przetwarzania.
| Metoda | Co zwraca | Kiedy jej używam |
|---|---|---|
test() |
true albo false
|
Gdy chcę tylko sprawdzić, czy dopasowanie istnieje |
exec() |
Tablicę z wynikiem albo null
|
Gdy potrzebuję grup przechwytujących lub indeksu dopasowania |
match() |
Wynik dopasowania z poziomu stringa | Gdy pracuję z pojedynczym dopasowaniem albo prostą listą wyników |
matchAll() |
Iterator wszystkich dopasowań | Gdy chcę przejść po wielu wynikach i zachować grupy |
replace() |
Nowy napis po zamianie | Gdy chcę podmienić fragment tekstu |
replaceAll() |
Nowy napis po zamianie wszystkich wystąpień | Gdy zamiana ma objąć każdy pasujący fragment |
search() |
Indeks pierwszego dopasowania | Gdy interesuje mnie tylko pozycja w tekście |
split() |
Tablicę fragmentów | Gdy chcę podzielić string według wzorca |
Ja najczęściej zaczynam od `test()`, jeśli potrzebuję samej odpowiedzi tak albo nie. Gdy chcę wyciągnąć dane, używam `exec()` albo `matchAll()`. To rozróżnienie jest ważne, bo `test()` bywa stateful przy flagach `g` i `y`, więc przy wielokrotnym użyciu warto pamiętać o `lastIndex`.
const text = "Zamówienia: ORD-1024 i ORD-2048";
const re = /ORD-(\d+)/g;
for (const match of text.matchAll(re)) {
console.log(match[0]); // ORD-1024, ORD-2048
console.log(match[1]); // 1024, 2048
}Do prostych zamian bardzo dobrze działa też `replace()`. Jeśli chcę znormalizować biały znak, zwykle sięgam po coś w stylu `/\s+/g`, a jeśli dzielę tekst na linie, często wystarcza `split(/\r?\n/)`. To są małe wzorce, ale w codziennej pracy robią dużą różnicę.
Przykłady, które rozwiązują codzienne zadania
Najlepiej uczy się regexów na krótkich, praktycznych przypadkach. Zamiast budować od razu rozbudowany wzorzec, wolę zacząć od konkretnego formatu i sprawdzić, czy da się go opisać możliwie prosto. W web developmencie to zwykle daje lepszy efekt niż próba napisania „idealnego” regexa za pierwszym podejściem.
Polski kod pocztowy
const postalCode = /^\d{2}-\d{3}$/;
postalCode.test("00-001"); // true
postalCode.test("00001"); // falseTen przykład jest prosty, ale dobrze pokazuje siłę kotwic `^` i `$`. Regex sprawdza cały string, a nie tylko jego fragment. To ważne, bo bez kotwic wzorzec mógłby zaakceptować tekst, który tylko częściowo pasuje.
Slug z tytułu artykułu
const title = "Jak pisać wyrażenia regularne w JavaScript";
const slug = title
.normalize("NFD")
.replace(/\p{Diacritic}/gu, "")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
console.log(slug); // jak-pisac-wyrazenia-regularne-w-javascriptTo jeden z tych przypadków, w których regex działa razem z innymi metodami stringa. Najpierw usuwam znaki diakrytyczne, potem zamieniam wszystkie niealfanumeryczne fragmenty na myślnik i na końcu czyszczę myślniki z brzegów. Taki łańcuch operacji jest czytelniejszy niż jeden ogromny wzorzec na wszystko.
Przeczytaj również: hasOwnProperty w JS – jak sprawdzić własność obiektu?
Wyciąganie numeru zgłoszenia z tekstu
const text = "Błąd opisano w zgłoszeniu TCK-4821 i powiązano z TCK-5100";
const tickets = text.match(/\bTCK-\d{4}\b/g) ?? [];
console.log(tickets); // ["TCK-4821", "TCK-5100"]Tu regex nie waliduje, tylko wyciąga konkretne fragmenty. To bardzo częste zastosowanie: logi, komentarze, wiadomości systemowe, import danych z CMS-a. Gdy format jest znany, można od razu sięgnąć po wzorzec i oszczędzić ręczne dzielenie tekstu.
W praktyce warto też pamiętać, że nie każdy pozornie prosty problem powinien kończyć się regexem. Walidacja adresu e-mail czy pełnego HTML-a to klasyczne przykłady, gdzie wzorzec szybko robi się zbyt kruchy albo zbyt długi.
Najczęstsze błędy, które widzę najczęściej
- Brak escapowania znaków specjalnych. Tekst użytkownika wstawiony bezpośrednio do regexa potrafi zmienić znaczenie wzorca.
- Mylenie dopasowania częściowego z pełnym. Bez kotwic regex może zwrócić poprawny fragment, choć cały string już nie pasuje.
- Zbyt zachłanne `.*`. To prosty sposób na pobranie za dużego kawałka tekstu.
- Ignorowanie stanu przy `g` i `y`. `test()` oraz `exec()` pamiętają `lastIndex`, więc przy pętli trzeba uważać na kolejne wywołania.
- Zakładanie, że `\w` i podobne skróty wystarczą dla każdego języka. W tekstach z polskimi znakami, emoji albo nazwami miast lepiej świadomie używać Unicode.
- Próba rozwiązania wszystkiego jednym wzorcem. Gdy reguł jest dużo, czytelniejszy bywa zwykły kod z kilkoma prostymi krokami.
Jest jeszcze jeden błąd, który w projektach webowych widzę regularnie: zbyt skomplikowane wzorce na niepewnych danych wejściowych. Zagnieżdżone powtórzenia mogą prowadzić do bardzo wolnego dopasowania, więc przy dłuższych tekstach i zewnętrznym inputcie wolę prostsze konstrukcje. Jeśli regex ma analizować dane z internetu, nie zakładam, że wejście będzie grzeczne.
To prowadzi do ostatniej kwestii, czyli do tego, jak utrzymać taki kod w projekcie, żeby nie zamienił się w nieczytelny jednorazowy skrót.
Jak trzymać regexy pod kontrolą w większym projekcie
Najlepszy regex to taki, który da się przeczytać po kilku miesiącach bez odpalania testów na ślepo. Ja zwykle zapisuję wzorce w stałych o nazwach mówiących, co sprawdzają, a nie jak są zbudowane. Jeśli reguła robi się dłuższa niż kilka linijek, rozbijam ją na mniejsze kroki zamiast upychać wszystko w jednej linii.
- Zapisuję wzorzec jako stałą, na przykład `const postalCodePattern = ...`, zamiast powielać go w kilku miejscach.
- Dodaję testy dla poprawnych i błędnych przykładów, bo to od razu pokazuje, czy regex jest zbyt luźny albo zbyt restrykcyjny.
- Sprawdzam teksty z polskimi znakami i emoji, jeśli aplikacja działa na danych użytkownika.
- Unikam „genialnych” wzorców, które są krótsze, ale trudne do utrzymania.
- Gdy coś ma zbyt wiele wyjątków, przenoszę logikę do zwykłego kodu i zostawiam regex tylko do prostego filtrowania.
Jeśli miałbym wskazać jedną praktykę, która najszybciej poprawia jakość pracy z regexami, byłoby to testowanie na realnych przykładach, a nie na jednym idealnym stringu. To właśnie wtedy widać, czy wzorzec rzeczywiście pomaga, czy tylko wygląda sprytnie. I to jest najrozsądniejszy punkt wyjścia do dalszej pracy z tekstem w JavaScript.