Пошук уроків, статей та іншого контенту
Навчитеся використовувати захоплювальні й незахоплювальні групи для отримання частин знайденого тексту.
Захоплювальна група в регулярному виразі дає змогу отримати окрему частину знайденого тексту. Групу створюють за допомогою круглих дужок:
const pattern = /(\d{4})-(\d{2})-(\d{2})/;У цьому виразі:
(\d{4}) — рік;
(\d{2}) — місяць;
(\d{2}) — день.
Кожна група отримує номер у порядку появи: 1, 2, 3.
Метод String.prototype.match() повертає масив, у якому:
елемент із індексом 0 містить весь збіг;
наступні елементи містять значення захоплювальних груп.
const text = "Дата релізу: 2026-07-26";
const pattern = /(\d{4})-(\d{2})-(\d{2})/;
const result = text.match(pattern);
console.log(result[0]); // "2026-07-26"
console.log(result[1]); // "2026"
console.log(result[2]); // "07"
console.log(result[3]); // "26"Групи дають змогу не просто перевірити, чи відповідає текст шаблону, а й розділити знайдений фрагмент на логічні частини.
exec()Метод RegExp.prototype.exec() також повертає масив із результатом пошуку та захопленими групами:
const pattern = /(\w+)@(\w+\.\w+)/;
const email = "Напишіть на адресу admin@example.com";
const result = pattern.exec(email);
if (result !== null) {
console.log(result[0]); // "admin@example.com"
console.log(result[1]); // "admin"
console.log(result[2]); // "example.com"
}На відміну від match(), exec() зручно використовувати, коли регулярний вираз зберігається в окремій змінній або коли потрібно послідовно обробляти всі збіги.
Круглі дужки не лише захоплюють текст, а й змінюють порядок застосування операторів. Наприклад:
/(cat|dog)s/Цей вираз означає:
знайти cat або dog;
після цього знайти s.
Але іноді потрібно лише згрупувати частини виразу, не зберігаючи результат групи. Для цього використовують незахоплювальну групу:
(?:pattern)Наприклад:
const pattern = /(?:https?:\/\/)?example\.com/;
console.log(pattern.test("example.com")); // true
console.log(pattern.test("https://example.com")); // true(?:https?:\/\/)? групує протокол і робить його необов’язковим, але не створює додаткової захоплювальної групи.
Це важливо, коли результат потрібно отримати за індексами:
const pattern = /(?:https?:\/\/)?(example\.com)/;
const result = "https://example.com".match(pattern);
console.log(result[0]); // "https://example.com"
console.log(result[1]); // "example.com"Якби протокол був звичайною групою, він зайняв би індекс 1, а домен опинився б в індексі 2.
Незахоплювальні групи корисні, коли:
потрібно згрупувати альтернативи;
потрібно застосувати квантифікатор до кількох символів;
внутрішній текст групи не потрібен у результаті;
потрібно уникнути зайвого зміщення номерів інших груп.
Приклад із квантифікатором:
const pattern = /(?:ha)+/;
console.log(pattern.test("haha")); // true
console.log(pattern.test("hahaha")); // trueГрупа (?:ha) повторюється один або більше разів, але самі повторення не повертаються як захоплені значення.
Числові індекси зручні для коротких виразів, але великий регулярний вираз із групами 1, 2, 3 може бути складно читати. JavaScript підтримує іменовані групи:
(?<name>pattern)Назва групи записується після ?<.
const text = "Дата релізу: 2026-07-26";
const pattern = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const result = text.match(pattern);
console.log(result.groups.year); // "2026"
console.log(result.groups.month); // "07"
console.log(result.groups.day); // "26"Об’єкт groups містить значення іменованих груп. Такий запис зрозуміліший, ніж звернення до result[1] або result[2].
Іменовані групи також залишаються доступними за числовими індексами:
console.log(result[1]); // "2026"
console.log(result[2]); // "07"
console.log(result[3]); // "26"Проте в прикладному коді краще використовувати назви, якщо вони роблять код зрозумілішим.
Зворотне посилання дає змогу перевірити, що далі в тексті повторюється значення, яке вже було захоплено групою.
Для числової групи використовують:
\1
\2
\3Наприклад, вираз для пошуку однакових слів, записаних двічі поспіль:
const pattern = /\b(\w+)\s+\1\b/;
console.log(pattern.test("дуже дуже")); // true
console.log(pattern.test("дуже швидко")); // falseРозгляньмо складові:
\b — межа слова;
(\w+) — захоплює слово;
\s+ — один або більше пробільних символів;
\1 — вимагає повторити текст першої групи;
другий \b — ще одна межа слова.
У рядку "дуже дуже" група (\w+) спочатку отримує значення "дуже", а \1 вимагає ще одне "дуже".
Якщо регулярний вираз створюють через RegExp, потрібно пам’ятати про екранування зворотної скісної риски:
const pattern = new RegExp("\\b(\\w+)\\s+\\1\\b");
console.log(pattern.test("go go")); // trueУ літералі регулярного виразу записують /\1/, а в рядку JavaScript — "\\1". Перша скісна риска потрібна JavaScript для екранування другої.
Для іменованої групи використовують синтаксис:
\k<name>Приклад:
const pattern = /\b(?<word>\w+)\s+\k<word>\b/;
console.log(pattern.test("test test")); // true
console.log(pattern.test("test case")); // falseЗворотне посилання \k<word> означає: знайдений текст має точно повторити значення групи word.
replace()Захоплювальні групи часто застосовують для перетворення тексту. У рядку заміни можна використати:
$1, $2, $3 — значення відповідних груп;
$<name> — значення іменованої групи;
$& — весь знайдений фрагмент.
const date = "2026-07-26";
const europeanDate = date.replace(
/(\d{4})-(\d{2})-(\d{2})/,
"$3.$2.$1"
);
console.log(europeanDate); // "26.07.2026"Групи захоплюють рік, місяць і день, а рядок заміни виводить їх у новому порядку.
Те саме можна записати з іменованими групами:
const date = "2026-07-26";
const europeanDate = date.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
"$<day>.$<month>.$<year>"
);
console.log(europeanDate); // "26.07.2026"Іменовані групи особливо корисні, коли порядок частин складний або вираз змінюється з часом.
Другим аргументом replace() може бути функція. Захоплені групи передаються їй після повного збігу:
const text = "Імена: Ivan, Olena, Mark";
const result = text.replace(
/\b([A-Z][a-z]+)\b/g,
(fullMatch, name) => name.toUpperCase()
);
console.log(result); // "Імена: IVAN, OLENA, MARK"У цьому прикладі:
fullMatch містить весь знайдений текст;
name містить значення першої захоплювальної групи;
прапорець g дозволяє обробити всі збіги.
Для іменованої групи функція отримує об’єкт groups серед останніх аргументів:
const text = "2026-07-26 і 2025-12-01";
const result = text.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/g,
(fullMatch, year, month, day, offset, wholeText, groups) => {
return `${groups.day}.${groups.month}.${groups.year}`;
}
);
console.log(result); // "26.07.2026 і 01.12.2025"Під час роботи з функцією заміни важливо враховувати порядок аргументів. Якщо потрібна лише іменована група, можна отримати останній аргумент динамічно:
const text = "2026-07-26";
const result = text.replace(
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/,
(...args) => {
const groups = args.at(-1);
return `${groups.day}.${groups.month}.${groups.year}`;
}
);
console.log(result); // "26.07.2026"Захоплювальні групи можна використовувати разом з оператором альтернативи |.
const pattern = /\b(https?|ftp):\/\/([a-z0-9.-]+)/i;
const result = "Сервер: https://example.com".match(pattern);
console.log(result[1]); // "https"
console.log(result[2]); // "example.com"Перша група захоплює протокол, а друга — ім’я хоста.
Якщо протокол потрібен лише для перевірки, але не потрібен у результаті, використовуйте незахоплювальну групу:
const pattern = /(?:https?|ftp):\/\/([a-z0-9.-]+)/i;
const result = "Сервер: https://example.com".match(pattern);
console.log(result[1]); // "example.com"Тепер домен має індекс 1, оскільки протокол не створює захопленої групи.
undefinedЯкщо захоплювальна група є необов’язковою і не бере участі в конкретному збігу, її значенням буде undefined.
const pattern = /(\+380)?\s?(\d{2})\s?(\d{3})\s?(\d{2})\s?(\d{2})/;
const result = "067 123 45 67".match(pattern);
console.log(result[1]); // undefined
console.log(result[2]); // "067"
console.log(result[3]); // "123"Група (\+380)? може бути відсутньою, тому її значення потрібно обробляти як необов’язкове:
const countryCode = result[1] ?? "без коду країни";
console.log(countryCode); // "без коду країни"g і захоплювальні групиПоведінка match() залежить від прапорця g.
Без g метод повертає перший збіг разом із захопленими групами:
const text = "ID: A12, ID: B34";
const result = text.match(/ID: ([A-Z])(\d+)/);
console.log(result[0]); // "ID: A12"
console.log(result[1]); // "A"
console.log(result[2]); // "12"З прапорцем g match() повертає всі повні збіги, але не повертає окремі групи:
const text = "ID: A12, ID: B34";
const result = text.match(/ID: ([A-Z])(\d+)/g);
console.log(result); // ["ID: A12", "ID: B34"]Якщо потрібно отримати групи для всіх збігів, використовуйте matchAll():
const text = "ID: A12, ID: B34";
const pattern = /ID: ([A-Z])(\d+)/g;
for (const result of text.matchAll(pattern)) {
console.log(result[0]); // повний збіг
console.log(result[1]); // літера
console.log(result[2]); // число
}matchAll() потребує регулярного виразу з прапорцем g. Його результат — ітерований об’єкт, який можна перебирати циклом for...of.
З іменованими групами приклад виглядає так:
const text = "ID: A12, ID: B34";
const pattern = /ID: (?<category>[A-Z])(?<number>\d+)/g;
for (const result of text.matchAll(pattern)) {
console.log(result.groups.category);
console.log(result.groups.number);
}Розглянемо функцію, яка розбирає версію формату major.minor.patch:
function parseVersion(version) {
const pattern = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$/;
const match = version.match(pattern);
if (match === null) {
return null;
}
return {
major: Number(match.groups.major),
minor: Number(match.groups.minor),
patch: Number(match.groups.patch),
};
}
console.log(parseVersion("2.15.4"));
// { major: 2, minor: 15, patch: 4 }
console.log(parseVersion("2.15"));
// nullСимволи ^ і $ гарантують, що весь рядок має відповідати формату. Без них вираз міг би знайти коректну версію лише як частину довшого рядка.
Не кожні дужки повинні створювати групу:
const pattern = /(https?|ftp):\/\/(example\.com)/;Якщо протокол не потрібно отримувати окремо, краще написати:
const pattern = /(?:https?|ftp):\/\/(example\.com)/;Це зменшує кількість груп і робить індекси передбачуванішими.
const result = "User: alice".match(/User: (\w+)/);
console.log(result[0]); // "User: alice"
console.log(result[1]); // "alice"result[0] — це весь збіг, а не перша захоплювальна група.
Додавання нової групи на початку змінює індекси всіх наступних груп:
const first = /(\d{4})-(\d{2})/;
const second = /(date: )(\d{4})-(\d{2})/;У першому виразі рік — це result[1], а в другому — result[2]. Для складних виразів іменовані групи зменшують ризик такої помилки.
Ці вирази мають різний зміст:
/(\w+)\s+\1/
/(\w+)\s+\w+/Перший вимагає повторення точно того самого слова:
"one one" // відповідає
"one two" // не відповідаєДругий дозволяє будь-яке друге слово:
"one one" // відповідає
"one two" // також відповідаєnullЯкщо збігу немає, match() повертає null:
const result = "hello".match(/(\d+)/);
if (result !== null) {
console.log(result[1]);
}Не звертайтеся одразу до result[1], якщо збіг може бути відсутнім.
\1 у рядку без екрануванняНеправильно:
const pattern = new RegExp("\b(\w+)\s+\1\b");У рядковому літералі скісні риски потрібно екранувати:
const pattern = new RegExp("\\b(\\w+)\\s+\\1\\b");Захоплювальна група записується як (pattern).
Повний збіг має індекс 0, а групи — індекси від 1.
Незахоплювальна група записується як (?:pattern).
Іменовану групу створюють синтаксисом (?<name>pattern).
Зворотні посилання \1, \2 перевіряють повторення значення групи.
Для іменованих груп використовують \k<name>.
У replace() групи доступні через $1, $2 або $<name>.
match() із прапорцем g повертає лише повні збіги.
matchAll() дає змогу отримати захоплені групи для всіх збігів.
Необов’язкові групи можуть мати значення undefined.
Для складних виразів іменовані групи зазвичай зрозуміліші та надійніші за числові індекси.