Avatar ·

Как получить доступ к совпавшим группам в регулярном выражении JavaScript?

Всем привет! Я новичок в JavaScript и работаю с регулярными выражениями. У меня есть строка, например, `"Сегодня 25.12.2023"`, и я хочу извлечь из неё день, месяц и год отдельно. Я написал такой код: ```javascript let str = "Сегодня 25.12.2023"; let regex = /(\d{2})\.(\d{2})\.(\d{4})/; let result = regex.exec(str); console.log(result); ``` В консоли я вижу массив, но не понимаю, как получить именно день, месяц и год. Я пробовал писать `result[1]`, `result[2]`, `result[3]` — это работает, но мне кажется, что это не очень удобно. К тому же, если я использую `str.match(regex)`, то результат отличается, когда нет совпадений. Объясните, пожалуйста, как правильно получать доступ к группам захвата в JavaScript? В каких случаях использовать `exec()`, а в каких `match()`? И можно ли как-то дать группам имена, чтобы не путаться в индексах? Буду благодарен за подробный разбор с примерами. <<>>

Основы работы с группами захвата в JavaScript

В регулярных выражениях группы захвата (capturing groups) — это части шаблона, заключённые в круглые скобки (). Они позволяют извлечь подстроки, соответствующие этим частям. В JavaScript есть несколько способов получить к ним доступ.

Метод exec()

Метод RegExp.prototype.exec() возвращает массив с совпадениями. Нулевой элемент — это полное совпадение, а элементы с 1 по N — это группы захвата:

let str = "Сегодня 25.12.2023";
let regex = /(\d{2})\.(\d{2})\.(\d{4})/;
let result = regex.exec(str);

console.log(result[0]); // "25.12.2023" (полное совпадение)
console.log(result[1]); // "25" (день)
console.log(result[2]); // "12" (месяц)
console.log(result[3]); // "2023" (год)

Если совпадений нет, exec() возвращает null, что удобно для проверки:

if (result) {
  console.log(`День: ${result[1]}, Месяц: ${result[2]}, Год: ${result[3]}`);
} else {
  console.log("Дата не найдена");
}

Метод match()

Метод String.prototype.match() ведёт себя по-разному в зависимости от флага g:

  • Без флага g: возвращает тот же массив, что и exec() — с группами захвата.
  • С флагом g: возвращает только полные совпадения без групп. Это частая ловушка для новичков.
// Без g — работает как exec()
let match1 = str.match(/(\d{2})\.(\d{2})\.(\d{4})/);
console.log(match1[1]); // "25"

// С g — только полные совпадения
let match2 = str.match(/(\d{2})\.(\d{2})\.(\d{4})/g);
console.log(match2); // ["25.12.2023"]
console.log(match2[1]); // undefined!

Именованные группы захвата (ES2018)

Начиная с ES2018, вы можете давать группам имена с помощью синтаксиса (?<name>...). Это делает код читаемее:

let regex = /(?<day>\d{2})\.(?<month>\d{2})\.(?<year>\d{4})/;
let result = regex.exec(str);

if (result) {
  console.log(result.groups.day);    // "25"
  console.log(result.groups.month);  // "12"
  console.log(result.groups.year);   // "2023"
}

Именованные группы также работают с match() (без флага g).

Глобальный поиск с exec() в цикле

Если нужно найти все совпадения с группами, используйте exec() в цикле while:

let str = "Даты: 25.12.2023, 01.01.2024, 15.06.2025";
let regex = /(\d{2})\.(\d{2})\.(\d{4})/g;
let match;

while ((match = regex.exec(str)) !== null) {
  console.log(`День: ${match[1]}, Месяц: ${match[2]}, Год: ${match[3]}`);
}

Метод matchAll() (ES2020)

Современная альтернатива — String.prototype.matchAll(). Он возвращает итератор, который можно обойти с помощью for...of или развернуть в массив:

let matches = str.matchAll(/(\d{2})\.(\d{2})\.(\d{4})/g);

for (let match of matches) {
  console.log(`День: ${match[1]}, Месяц: ${match[2]}, Год: ${match[3]}`);
}

// Или с именованными группами
let namedMatches = str.matchAll(/(?<day>\d{2})\.(?<month>\d{2})\.(?<year>\d{4})/g);
for (let match of namedMatches) {
  console.log(match.groups.day);
}

Краткие рекомендации

  • Используйте exec() для одного совпадения с проверкой на null.
  • Используйте match() без g если вам не нужен глобальный поиск.
  • Используйте matchAll() или exec() в цикле для глобального поиска с группами.
  • Применяйте именованные группы (?<name>...) для повышения читаемости.
<<>>

Деструктуризация для удобства

Если вы не хотите каждый раз писать result[1], можно использовать деструктуризацию массива. Это особенно удобно, когда групп немного:

let str = "25.12.2023";
let [, day, month, year] = str.match(/(\d{2})\.(\d{2})\.(\d{4})/) || [];
console.log(day, month, year); // "25" "12" "2023"

Обратите внимание на запятую в начале — она пропускает нулевой элемент (полное совпадение). Оператор || [] защищает от ошибки, если совпадений нет.

Метод replace() с функцией

Иногда удобно использовать String.prototype.replace() с колбэком. Второй аргумент может быть функцией, которая получает группы как отдельные параметры:

let str = "25.12.2023";
let result = {};

str.replace(/(\d{2})\.(\d{2})\.(\d{4})/, (match, day, month, year) => {
  result = { day, month, year };
});

console.log(result); // { day: "25", month: "12", year: "2023" }

Этот подход полезен, когда нужно одновременно извлечь данные и выполнить замену в строке.

Библиотека XRegExp для продвинутых случаев

Если вам нужны дополнительные возможности (например, поддержка Unicode или расширенный синтаксис), обратите внимание на библиотеку XRegExp. Она добавляет именованные группы для старых браузеров и другие полезные функции.