20. září 2026

Zachytávací skupiny

Část vzoru můžeme uzavřít do závorek (...). Tato část se pak nazývá „zachytávací skupina“.

Má dva efekty:

  1. Umožňuje nám získat část shody jako samostatný prvek pole výsledků.
  2. Jestliže za závorky umístíme kvantifikátor, aplikuje se na závorky jako na celek.

Příklady

Na příkladech se podívejme, jak závorky fungují.

Příklad: gogogo

Vzor go+ bez závorek znamená znak g, po němž následuje jednou nebo vícekrát opakované o, například goooo nebo gooooooooo.

Závorky seskupují znaky dohromady, takže (go)+ znamená go, gogo, gogogo a tak dále.

alert( 'Gogogo teď!'.match(/(go)+/ig) ); // "Gogogo"

Příklad: doména

Vytvořme něco složitějšího – regulární výraz pro hledání domény webového sídla.

Příklad:

mail.com
users.mail.com
smith.users.mail.com

Jak vidíme, doména se skládá z opakovaných slov a po každém z nich kromě posledního následuje tečka.

V regulárních výrazech to je (\w+\.)+\w+:

let rv = /(\w+\.)+\w+/g;

alert( "site.com my.site.com".match(rv) ); // site.com,my.site.com

Hledání funguje, ale vzor nenajde doménu s pomlčkou, např. my-site.com, protože pomlčka nepatří do třídy \w.

Můžeme to opravit nahrazením \w za [\w-] v každém slově kromě posledního: ([\w-]+\.)+\w+.

Příklad: e-mail

Předchozí příklad můžeme rozšířit. Můžeme na jeho základě vytvořit regulární výraz pro e-maily.

Formát e-mailu je: jméno@doména. Jméno může být libovolné slovo, jsou povoleny i pomlčky a tečky. V regulárních výrazech to je [-.\w]+.

Vzor:

let rv = /[-.\w]+@([\w-]+\.)+[\w-]+/g;

alert("my@mail.com @ his@site.com.uk".match(rv)); // my@mail.com, his@site.com.uk

Tento regulární výraz není dokonalý, ale většinou funguje a pomáhá opravovat neúmyslné překlepy. Jediná skutečně spolehlivá kontrola e-mailu je poslat na něj zprávu.

Obsah závorek ve shodě

Závorky jsou číslovány zleva doprava. Vyhledávací motor si pamatuje obsah každé z nich v nalezené shodě a umožňuje ho načíst ve výsledku.

Metoda řetězec.match(rv), pokud rv nemá příznak g, najde první shodu a vrátí ji jako pole:

  1. Na indexu 0: celá shoda.
  2. Na indexu 1: obsah prvních závorek.
  3. Na indexu 2: obsah druhých závorek.
  4. …a tak dále…

Například chceme najít HTML značky <.*?> a zpracovat je. Bylo by vhodné mít obsah značky (to, co je v lomených závorkách) v samostatné proměnné.

Uzavřeme vnitřní obsah do závorek: <(.*?)>.

Nyní ve výsledném poli získáme jak značku jako celek <h1>, tak její obsah h1:

let řetězec = '<h1>Ahoj, světe!</h1>';

let značka = řetězec.match(/<(.*?)>/);

alert( značka[0] ); // <h1>
alert( značka[1] ); // h1

Vnořené skupiny

Závorky mohou být vnořené. I v takovém případě se číslují zleva doprava.

Například když hledáme značku ve <span class="moje">, může nás zajímat:

  1. Obsah značky jako celek: span class="moje".
  2. Název značky: span.
  3. Atributy značky: class="moje".

Přidejme pro ně závorky: <(([a-z]+)\s*([^>]*))>.

Budou očíslovány následovně (zleva doprava podle otevírací závorky):

V akci:

let řetězec = '<span class="moje">';

let rv = /<(([a-z]+)\s*([^>]*))>/;

let výsledek = řetězec.match(rv);
alert(výsledek[0]); // <span class="moje">
alert(výsledek[1]); // span class="moje"
alert(výsledek[2]); // span
alert(výsledek[3]); // class="moje"

Nulový index pole výsledek obsahuje vždy celou shodu.

Pak následují skupiny, očíslované zleva doprava podle otevírací závorky. První skupina je vrácena ve výsledek[1]. V tomto případě obsahuje celý obsah značky.

Pak výsledek[2] obsahuje skupinu ze druhé otevírací závorky ([a-z]+) – název značky, pak výsledek[3] obsahuje značku: ([^>]*).

Obsah každé skupiny v řetězci:

Nepovinné skupiny

I když je skupina nepovinná a ve shodě se nevyskytuje (např. má kvantifikátor (...)?), odpovídající prvek pole výsledek bude přítomen a bude se rovnat undefined.

Uvažujme například regulární výraz a(z)?(c)?. Hledá "a", po němž může následovat "z", po němž může následovat "c".

Pokud jej spustíme na řetězci obsahujícím jediné písmeno a, výsledek bude:

let shoda = 'a'.match(/a(z)?(c)?/);

alert( shoda.length ); // 3
alert( shoda[0] ); // a (celá shoda)
alert( shoda[1] ); // undefined
alert( shoda[2] ); // undefined

Pole má délku 3, ale všechny skupiny jsou prázdné.

A zde je složitější shoda pro řetězec ac:

let shoda = 'ac'.match(/a(z)?(c)?/)

alert( shoda.length ); // 3
alert( shoda[0] ); // ac (celá shoda)
alert( shoda[1] ); // undefined, protože pro (z)? tam nic není
alert( shoda[2] ); // c

Délka pole je stále stejná: 3. Pro skupinu (z)? tam však nic není, takže výsledek je ["ac", undefined, "c"].

Hledání všech shod se skupinami: matchAll

matchAll je nová metoda, možná bude zapotřebí polyfill

Metoda matchAll není podporována ve starých prohlížečích.

Může být zapotřebí polyfill, například https://github.com/ljharb/String.prototype.matchAll.

Když hledáme všechny shody (příznak g), metoda match nevrací obsahy skupin.

Najděme například všechny značky v řetězci:

let řetězec = '<h1> <h2>';

let značky = řetězec.match(/<(.*?)>/g);

alert( značky ); // <h1>,<h2>

Výsledkem je pole shod, ale bez detailů o každé z nich. V praxi však zpravidla ve výsledku potřebujeme obsahy zachytávacích skupin.

Abychom je získali, měli bychom hledat metodou řetězec.matchAll(rv).

Byla do JavaScriptu přidána až dlouho po match jako její „nová a vylepšená verze“.

Hledá shody stejně jako match, ale jsou tady 3 rozdíly:

  1. Nevrací pole, ale iterovatelný objekt.
  2. Pokud je uveden příznak g, vrací každou shodu jako pole se skupinami.
  3. Nejsou-li žádné shody, nevrací null, ale prázdný iterovatelný objekt.

Příklad:

let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

// výsledky není pole, ale iterovatelný objekt
alert(výsledky); // [object RegExp String Iterator]

alert(výsledky[0]); // undefined (*)

výsledky = Array.from(výsledky); // změníme ho na pole

alert(výsledky[0]); // <h1>,h1 (1. značka)
alert(výsledky[1]); // <h2>,h2 (2. značka)

Jak vidíme, první rozdíl je velmi důležitý, jak je ukázáno na řádku (*). Nemůžeme načíst shodu jako výsledky[0], protože tento objekt je pseudopole. Můžeme z něj udělat opravdové Array pomocí Array.from. Další podrobnosti o pseudopolích a iterovatelných objektech naleznete v článku Iterovatelné objekty.

Pokud výsledky procházíme v cyklu, není Array.from nutné:

let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

for(let výsledek of výsledky) {
  alert(výsledek);
  // první alert: <h1>,h1
  // druhý: <h2>,h2
}

…Nebo při použití destrukturace:

let [značka1, značka2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

Každá shoda, vrácená metodou matchAll, má stejný formát, jako by ji vrátila metoda match bez příznaku g: je to pole s dalšími vlastnostmi index (index shody v řetězci) a input (zdrojový řetězec):

let výsledky = '<h1> <h2>'.matchAll(/<(.*?)>/gi);

let [značka1, značka2] = výsledky;

alert( značka1[0] ); // <h1>
alert( značka1[1] ); // h1
alert( značka1.index ); // 0
alert( značka1.input ); // <h1> <h2>
Proč je výsledkem matchAll iterovatelný objekt a ne pole?

Proč je tato metoda navržena zrovna takto? Důvod je jednoduchý – kvůli optimalizaci.

Volání matchAll neprovádí hledání. Místo toho vrátí iterovatelný objekt, zpočátku bez výsledků. Hledání je prováděno pokaždé, kdy nad ním iterujeme, např. v cyklu.

Bude tedy nalezeno tolik výsledků, kolik potřebujeme, ne více.

Například v textu může být 100 shod, ale v cyklu for..of nalezneme 5 z nich a pak se rozhodneme, že to stačí, a zavoláme break. Motor pak nebude ztrácet čas hledáním dalších 95 shod.

Pojmenované skupiny

Pamatovat si skupiny podle čísel je těžké. U jednoduchých vzorů to dokážeme, ale u složitějších je počítání závorek nepraktické. Máme mnohem lepší možnost: pojmenovat závorky.

Učiníme to uvedením ?<jméno> hned za otevírací závorkou.

Hledejme například datum ve formátu „rok-měsíc-den“:

let rvDatum = /(?<rok>[0-9]{4})-(?<měsíc>[0-9]{2})-(?<den>[0-9]{2})/;
let řetězec = "2019-04-30";

let skupiny = řetězec.match(rvDatum).groups;

alert(skupiny.rok); // 2019
alert(skupiny.měsíc); // 04
alert(skupiny.den); // 30

Jak vidíte, skupiny se nacházejí ve vlastnosti shody .groups.

Chceme-li najít všechna data, můžeme přidat příznak g.

Potřebujeme také matchAll k získání celých shod společně se skupinami:

let rvDatum = /(?<rok>[0-9]{4})-(?<měsíc>[0-9]{2})-(?<den>[0-9]{2})/g;

let řetězec = "2019-10-30 2020-01-01";

let výsledky = řetězec.matchAll(rvDatum);

for(let výsledek of výsledky) {
  let {rok, měsíc, den} = výsledek.groups;

  alert(`${den}.${měsíc}.${rok}`);
  // první alert: 30.10.2019
  // druhý: 01.01.2020
}

Zachytávací skupiny při nahrazování

Metoda řetězec.replace(rv, náhrada), která nahrazuje všechny shody s regulárním výrazem rv v řetězci řetězec, umožňuje použít obsah závorek v řetězci náhrada. To se provádí pomocí $n, kde n je číslo skupiny.

Příklad:

let řetězec = "Jan Novak";
let rv = /(\w+) (\w+)/;

alert( řetězec.replace(rv, '$2, $1') ); // Novak, Jan

Odkaz na pojmenované závorky bude $<jméno>.

Například přeformátujme data z formátu „rok-měsíc-den“ na „den.měsíc.rok“:

let rv = /(?<rok>[0-9]{4})-(?<měsíc>[0-9]{2})-(?<den>[0-9]{2})/g;

let řetězec = "2019-10-30, 2020-01-01";

alert( řetězec.replace(rv, '$<den>.$<měsíc>.$<rok>') );
// 30.10.2019, 01.01.2020

Nezachytávací skupiny s ?:

Někdy potřebujeme závorky k tomu, abychom správně aplikovali kvantifikátor, ale jejich obsah nechceme mít ve výsledcích.

Skupinu můžeme vyloučit z výsledků uvedením ?: na jejím začátku.

Například když chceme najít (go)+, ale nechceme mít obsah závorek (go) jako samostatný prvek pole, můžeme napsat: (?:go)+.

V následujícím příkladu získáme jako samostatný prvek shody jedině jméno Jan:

let řetězec = "Gogogo Jan!";

// ?: vyloučí 'go' ze zachytávání
let rv = /(?:go)+ (\w+)/i;

let výsledek = řetězec.match(rv);

alert( výsledek[0] ); // Gogogo Jan (celá shoda)
alert( výsledek[1] ); // Jan
alert( výsledek.length ); // 2 (pole neobsahuje další prvky)

Shrnutí

Závorky seskupují dohromady část regulárního výrazu, takže kvantifikátor se na ně aplikuje jako na celek.

Závorkové skupiny jsou očíslovány zleva doprava a mohou být pojmenovány pomocí (?<jméno>...).

Obsah, kterému odpovídá skupina, můžeme získat ve výsledcích:

  • Metoda řetězec.match vrací zachytávací skupiny jen bez příznaku g.
  • Metoda řetězec.matchAll vrací zachytávací skupiny vždy.

Pokud závorky nemají jméno, je jejich obsah dostupný v poli shod podle jejich čísla. Pojmenované závorky jsou k dispozici i ve vlastnosti groups.

Obsah závorek můžeme použít i v nahrazovacím řetězci v metodě řetězec.replace: podle čísla $n nebo jména $<jméno>.

Skupinu můžeme vyloučit z číslování uvedením ?: na jejím začátku. To používáme, když potřebujeme aplikovat kvantifikátor na celou skupinu, ale nechceme ji jako samostatný prvek v poli výsledků. Na takové závorky se také nemůžeme odkazovat v nahrazovacím řetězci.

Úlohy

MAC adresa síťového rozhraní se skládá ze 6 dvouciferných hexadecimálních čísel oddělených dvojtečkou.

Příklad: '01:32:54:67:89:AB'.

Napište regulární výraz, který ověří, zda řetězec je MAC adresa.

Použití:

let rv = /váš RV/;

alert( rv.test('01:32:54:67:89:AB') ); // true

alert( rv.test('0132546789AB') ); // false (bez dvojteček)

alert( rv.test('01:32:54:67:89') ); // false (5 čísel, musí být 6)

alert( rv.test('01:32:54:67:89:ZZ') ) // false (ZZ na konci)

Dvouciferné hexadecimální číslo je [0-9a-f]{2} (předpokládáme, že je nastaven příznak i).

Potřebujeme toto číslo NN a pak :NN opakované pětkrát (další čísla).

Regulární výraz je: [0-9a-f]{2}(:[0-9a-f]{2}){5}

Nyní dosáhneme toho, aby shoda zachytávala celý text: začala na začátku a skončila na konci. To učiníme tak, že vzor obklopíme mezi ^...$.

Nakonec:

let rv = /^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/i;

alert( rv.test('01:32:54:67:89:AB') ); // true

alert( rv.test('0132546789AB') ); // false (bez dvojteček)

alert( rv.test('01:32:54:67:89') ); // false (5 čísel, musí být 6)

alert( rv.test('01:32:54:67:89:ZZ') ) // false (ZZ na konci)

Napište RV, který najde barvy ve formátu #abc nebo #abcdef. To znamená: # následované 3 nebo 6 hexadecimálními číslicemi.

Příklad použití:

let rv = /váš RV/g;

let řetězec = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( řetězec.match(rv) ); // #3f3 #AA00ef

P.S. Počet hexadecimálních číslic by měl být přesně 3 nebo 6. Hodnoty se 4 číslicemi, např. #abcd, by se neměly shodovat.

Regulární výraz hledající 3-cifernou barvu #abc: /#[a-f0-9]{3}/i.

Můžeme přidat přesně 3 další nepovinné hexadecimální číslice. Nepotřebujeme jich více ani méně. Barva má buď 3, nebo 6 číslic.

Použijme k tomu kvantifikátor {1,2}: získáme /#([a-f0-9]{3}){1,2}/i.

Zde je vzor [a-f0-9]{3} uzavřen do závorek, aby se na něj aplikoval kvantifikátor {1,2}.

V akci:

let rv = /#([a-f0-9]{3}){1,2}/gi;

let řetězec = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( řetězec.match(rv) ); // #3f3 #AA00ef #abc

Je tady malý problém: vzor nalezl #abc v #abcd. Abychom tomu zabránili, můžeme na konec přidat \b:

let rv = /#([a-f0-9]{3}){1,2}\b/gi;

let řetězec = "color: #3f3; background-color: #AA00ef; and: #abcd";

alert( řetězec.match(rv) ); // #3f3 #AA00ef

Napište RV, který hledá všechna desetinná čísla včetně celých čísel, čísel s pohyblivou řádovou tečkou a záporných čísel.

Příklad použití:

let rv = /váš RV/g;

let řetězec = "-1.5 0 2 -123.4.";

alert( řetězec.match(rv) ); // -1.5, 0, 2, -123.4

Kladné číslo s nepovinnou desetinnou částí je: \d+(\.\d+)?.

Přidejme na začátek nepovinnou -:

let rv = /-?\d+(\.\d+)?/g;

let řetězec = "-1.5 0 2 -123.4.";

alert( řetězec.match(rv) );   // -1.5, 0, 2, -123.4

Aritmetický výraz se skládá ze 2 čísel a operátoru mezi nimi, například:

  • 1 + 2
  • 1.2 * 3.4
  • -3 / -6
  • -2 - 2

Operátor je jeden z následujících: "+", "-", "*" nebo "/".

Na začátku, na konci i mezi jednotlivými částmi mohou být dodatečné mezery.

Vytvořte funkci parsuj(výraz), která vezme výraz a vrátí pole 3 prvků:

  1. První číslo.
  2. Operátor.
  3. Druhé číslo.

Příklad:

let [a, op, b] = parsuj("1.2 * 3.4");

alert(a); // 1.2
alert(op); // *
alert(b); // 3.4

Regulární výraz pro číslo je: -?\d+(\.\d+)?. Vytvořili jsme ho v předchozí úloze.

Operátor je [-+*/]. Pomlčka - je v hranatých závorkách uvedena jako první, protože uprostřed by znamenala rozsah znaků, ale my potřebujeme právě znak -.

Lomítko / by mělo být v JavaScriptovém RV /.../ předznamenáno únikovým znakem, uděláme to později.

Potřebujeme číslo, operátor a pak další číslo. A nepovinné mezery mezi nimi.

Celý regulární výraz: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)?.

Má 3 části a mezi nimi je \s*:

  1. -?\d+(\.\d+)? – první číslo,
  2. [-+*/] – operátor,
  3. -?\d+(\.\d+)? – druhé číslo.

Aby z každé z těchto částí vznikl samostatný prvek v poli výsledků, uzavřeme je do závorek: (-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?).

V akci:

let rv = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/;

alert( "1.2 + 12".match(rv) );

Výsledek obsahuje:

  • výsledek[0] == "1.2 + 12" (celá shoda)
  • výsledek[1] == "1.2" (první skupina (-?\d+(\.\d+)?) – první číslo včetně desetinné části)
  • výsledek[2] == ".2" (druhá skupina(\.\d+)? – první desetinná část)
  • výsledek[3] == "+" (třetí skupina ([-+*\/]) – operátor)
  • výsledek[4] == "12" (čtvrtá skupina (-?\d+(\.\d+)?) – druhé číslo)
  • výsledek[5] == undefined (pátá skupina (\.\d+)? – poslední desetinná část chybí, takže je undefined)

Chceme jen čísla a operátor, ne celou shodu nebo desetinné části, takže výsledek trochu „pročistíme“.

Celou shodu (první prvek pole) můžeme odstranit posunem pole výsledek.shift().

Skupiny obsahující desetinné části (skupiny 2 a 4) (.\d+) můžeme vyloučit přidáním ?: na začátek: (?:\.\d+)?.

Konečné řešení:

function parsuj(výraz) {
  let rv = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/;

  let výsledek = výraz.match(rv);

  if (!výsledek) return [];
  výsledek.shift();

  return výsledek;
}

alert( parsuj("-1.23 * 3.45") );  // -1.23, *, 3.45

Alternativou k použití nezachytávacího ?: by bylo pojmenování skupin, například:

function parsuj(výraz) {
  let rv = /(?<a>-?\d+(?:\.\d+)?)\s*(?<operátor>[-+*\/])\s*(?<b>-?\d+(?:\.\d+)?)/;

  let výsledek = výraz.match(rv);

  return [výsledek.groups.a, výsledek.groups.operátor, výsledek.groups.b];
}

alert( parsuj("-1.23 * 3.45") );  // -1.23, *, 3.45;
Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)