12. června 2026

Dopředné a zpětné nahlédnutí

Někdy potřebujeme najít jen ty shody se vzorem, které se nacházejí před nebo za jiným vzorem.

K tomu existuje speciální syntaxe, nazývaná „dopředné nahlédnutí“ („lookahead“) a „zpětné nahlédnutí“ („lookbehind“). Obě společně se nazývají „nahlédnutí“ („lookaround“).

Pro začátek najděme cenu v řetězci, např. 1 krocan stojí 30€. To znamená: číslo, po němž následuje znak €.

Dopředné nahlédnutí

Syntaxe je: X(?=Y), znamená „najdi X, ale jen pokud za ním následuje Y“. Na místě X a Y může být libovolný vzor.

Pro celé číslo následované znakem € bude regulární výraz \d+(?=€):

let řetězec = "1 krocan stojí 30€";

alert( řetězec.match(/\d+(?=€)/) ); // 30, číslo 1 se ignoruje, neboť po něm nenásleduje €

Prosíme všimněte si, že nahlédnutí je pouhý test, obsah závorek (?=...) nebude do výsledku 30 zahrnut.

Když hledáme X(?=Y), motor regulárních výrazů najde X a pak ověří, zda okamžitě za ním následuje Y. Pokud ne, potenciální shoda se přeskočí a hledání pokračuje.

Jsou možné i složitější testy, např. X(?=Y)(?=Z) znamená:

  1. Najdi X.
  2. Ověř, zda Y je okamžitě za X (pokud ne, přeskoč ho).
  3. Ověř, zda Z je také okamžitě za X (pokud ne, přeskoč ho).
  4. Pokud oba testy prošly, je X shoda, jinak pokračuj v hledání.

Jinými slovy, takový vzor znamená, že hledáme X, po němž následují současně Y a Z.

To je možné jen tehdy, pokud se vzory Y a Z navzájem nevylučují.

Například \d+(?=\s)(?=.*30) hledá \d+, po němž následuje mezera (?=\s) a někde za ním je 30 (?=.*30):

let řetězec = "1 krocan stojí 30€";

alert( řetězec.match(/\d+(?=\s)(?=.*30)/) ); // 1

V našem řetězci tomu přesně odpovídá číslo 1.

Negativní dopředné hledání

Řekněme, že chceme ve stejném řetězci hledat množství, ne cenu. To znamená číslo \d+, po němž NENÁSLEDUJE €.

K tomu můžeme použít negativní dopředné hledání.

Syntaxe je: X(?!Y), znamená „najdi X, ale jen pokud za ním nenásleduje Y“.

let řetězec = "2 krocani stojí 60€";

alert( řetězec.match(/\d+\b(?!€)/g) ); // 2 (cena neodpovídá)

Zpětné hledání

Kompatibilita prohlížečů se zpětným hledáním

Prosíme všimněte si: Zpětné hledání není podporováno v prohlížečích, které nejsou postaveny na V8, např. Safari nebo Internet Explorer.

Dopředné hledání umožňuje přidat podmínku pro to, „co následuje“.

Zpětné hledání funguje podobně, ale dívá se dozadu. To znamená, že umožňuje najít vzor jen tehdy, pokud je něco před ním.

Syntaxe je:

  • Pozitivní zpětné hledání: (?<=Y)X, najde X, ale jen pokud před ním je Y.
  • Negativní zpětné hledání: (?<!Y)X, najde X, ale jen pokud před ním není Y.

Změňme například cenu na americké dolary. Znak dolaru se obvykle uvádí před číslem, takže pro hledání $30 použijeme (?<=\$)\d+ – číslo, před kterým je $:

let řetězec = "1 krocan stojí $30";

// znak dolaru je za únikovým znakem \$
alert( řetězec.match(/(?<=\$)\d+/) ); // 30 (samostatné číslo přeskočíme)

A pokud potřebujeme množství – číslo, před nímž není $, můžeme použít negativní zpětné hledání (?<!\$)\d+:

let řetězec = "2 krocani stojí $60";

alert( řetězec.match(/(?<!\$)\b\d+/g) ); // 2 (cena neodpovídá)

Zachytávací skupiny

Obsah uvnitř závorek obklopujících nahlédnutí se obvykle nestává součástí výsledku.

Například ve vzoru \d+(?=€) nebude znak € zachycen jako součást shody. To je přirozené: hledáme číslo \d+, zatímco (?=€) je jen test, že by po něm mělo následovat €.

V některých situacích však můžeme chtít zachytit i nahlížený výraz nebo jeho část. I to je možné. Jednoduše uzavřeme tuto část do dalších závorek.

V následujícím příkladu je znak měny (€|kr) zachycen společně s částkou:

let řetězec = "1 krocan stojí 30€";
let rv = /\d+(?=(€|kr))/; // další závorky kolem €|kr

alert( řetězec.match(rv) ); // 30, €

A zde je totéž pro zpětné nahlédnutí:

let řetězec = "1 krocan stojí $30";
let rv = /(?<=(\$|£))\d+/;

alert( řetězec.match(rv) ); // 30, $

Shrnutí

Dopředné a zpětné nahlédnutí (společně nazývaná „nahlédnutí“) jsou užitečná, když chceme najít něco, co závisí na kontextu před/za sebou.

U jednoduchých regulárních výrazů můžeme něco podobného udělat ručně. To znamená: najít všechno v jakémkoli kontextu a pak výsledky filtrovat podle kontextu v cyklu.

Nezapomeňte, že řetězec.match (bez příznaku g) a řetězec.matchAll (vždy) vrátí shody jako pole s vlastností index, takže přesně víme, kde v textu se shoda nachází, a můžeme zkontrolovat kontext.

Nahlédnutí je však obecně vhodnější.

Druhy nahlédnutí:

Vzor Typ Najde
X(?=Y) Pozitivní dopředné nahlédnutí X, pokud za ním je Y
X(?!Y) Negativní dopředné nahlédnutí X, pokud za ním není Y
(?<=Y)X Pozitivní zpětné nahlédnutí X, pokud před ním je Y
(?<!Y)X Negativní zpětné nahlédnutí X, pokud před ním není Y

Úlohy

Máme řetězec celých čísel.

Vytvořte regulární výraz, který mezi nimi najde jen nezáporná čísla (včetně nuly).

Příklad použití:

let rv = /váš RV/g;

let řetězec = "0 12 -5 123 -18";

alert( řetězec.match(rv) ); // 0, 12, 123

Regulární výraz pro celé číslo je \d+.

Záporná čísla můžeme vyloučit tak, že před něj umístíme negativní zpětné nahlédnutí: (?<!-)\d+.

Když to však nyní vyzkoušíme, můžeme si všimnout jednoho výsledku „navíc“:

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

let řetězec = "0 12 -5 123 -18";

console.log( řetězec.match(rv) ); // 0, 12, 123, 8

Jak vidíte, našel i 8 z -18. Abychom to vyloučili, musíme zajistit, že RV nezačne hledat shodu uprostřed jiného (neodpovídajícího) čísla.

Můžeme to udělat uvedením dalšího negativního zpětného nahlédnutí: (?<!-)(?<!\d)\d+. Nyní (?<!\d) zajistí, že shoda nebude začínat po jiné číslici, což je přesně to, co potřebujeme.

Můžeme je také spojit do jediného zpětného nahlédnutí následovně:

let rv = /(?<![-\d])\d+/g;

let řetězec = "0 12 -5 123 -18";

alert( řetězec.match(rv) ); // 0, 12, 123

Máme řetězec s HTML dokumentem.

Napište regulární výraz, který vloží <h1>Ahoj</h1> hned za značku <body>. Tato značka může obsahovat atributy.

Například:

let rv = /váš regulární výraz/;

let řetězec = `
<html>
  <body style="height: 200px">
  ...
  </body>
</html>
`;

řetězec = řetězec.replace(rv, `<h1>Ahoj</h1>`);

Pak by hodnota proměnné řetězec měla být následující:

<html>
  <body style="height: 200px"><h1>Ahoj</h1>
  ...
  </body>
</html>

Abychom něco vložili za značku <body>, musíme ji napřed najít. Můžeme k tomu použít vzor regulárního výrazu <body.*?>.

V této úloze nemusíme měnit značku <body>. Potřebujeme jen vložit text za ni.

Můžeme to udělat následovně:

let řetězec = '...<body style="...">...';
řetězec = řetězec.replace(/<body.*?>/, '$&<h1>Ahoj</h1>');

alert(řetězec); // ...<body style="..."><h1>Ahoj</h1>...

V nahrazovacím řetězci $& znamená samotnou shodu, tedy část zdrojového textu, která odpovídá <body.*?>. Bude nahrazena sama sebou plus <h1>Ahoj</h1>.

Alternativou je použít zpětné nahlédnutí:

let řetězec = '...<body style="...">...';
řetězec = řetězec.replace(/(?<=<body.*?>)/, `<h1>Ahoj</h1>`);

alert(řetězec); // ...<body style="..."><h1>Ahoj</h1>...

Jak vidíte, tento regulární výraz obsahuje pouze nahlížecí část.

Funguje následovně:

  • Na každé pozici v textu.
  • Ověří, zda jí předchází <body.*?>.
  • Pokud ano, máme shodu.

Značka <body.*?> nebude vrácena. Výsledkem tohoto RV je doslova prázdný řetězec, ale shoda se najde jen na pozicích, před kterými se nachází <body.*?>.

Nahrazuje tedy „prázdný řádek“, jemuž předchází <body.*?>, za <h1>Ahoj</h1>. To je vložení za <body>.

P.S. Užitečné mohou být i příznaky RV, konkrétně s nebo i: /<body.*?>/si. Příznak s způsobí, že tečka . najde znak nového řádku, a příznak i způsobí, že <body> najde i <BODY> bez ohledu na malá a velká písmena.

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…)