Обратные ссылки в регулярных выражениях: \n и \k<name>
Узнайте об обратных ссылках в JavaScript: что это такое, как использовать \1, \k<name> в паттерне и $1, $<name> в replace().
Большинство частей регулярного выражения совпадают с фиксированным текстом, но иногда нужно найти текст, который должен быть идентичен тому, что уже было найдено раньше — не зная заранее, каким именно будет этот текст. Именно здесь на помощь приходит обратная ссылка: она позволяет паттерну сказать «сопоставь то же самое, что только что захватила группа».
Типичные задачи для обратных ссылок — обнаружение удвоенного слова (the the), поиск строки, обёрнутой в парные кавычки ("..." или '...', но не "...'), или проверка того, что HTML-подобный тег закрыт тем же именем тега. Ничего из этого невозможно сделать с помощью обычных литеральных паттернов, поскольку текст для сопоставления неизвестен до момента выполнения регулярного выражения.
В этом руководстве рассматриваются нумерованные обратные ссылки (\1, \2, …), именованные обратные ссылки (\k<name>), правила нумерации групп, типичные подводные камни и повторное использование захваченного текста в String.prototype.replace().
Как использовать обратные ссылки
Внутри паттерна обратный слеш, за которым следует число, ссылается на текст, захваченный захватывающей группой. \1 — это то, что совпало с группой 1, \2 — с группой 2, и так далее. Ключевой момент: обратная ссылка совпадает с захваченным текстом, а не повторно применяет паттерн группы.
Здесь (\w+) захватывает слово в группу 1, \s совпадает с пробелом, а \1 требует того же слова снова. Поэтому hello hello совпадает, а hello world — нет: \1 должна быть равна тому, что захватила группа 1, а не просто снова совпасть с \w+.
Как нумеруются группы
Номера групп назначаются по позиции открывающей скобки каждой группы, слева направо. Это важно при наличии нескольких или вложенных групп:
Всё совпадение — это группа 0 (m[0]), поэтому первая захватывающая группа — \1, а не \0. Для вложенных групп внешняя группа получает меньший номер, поскольку её ( встречается первой.
Использование именованных групп
Нумерованные ссылки трудно читать, когда паттерн разрастается. Вместо этого можно назвать группу с помощью (?<name>…) и ссылаться на неё через \k<name>. Подробнее об объявлении именованных групп см. в разделе Захватывающие группы.
Здесь (?<word>\w+) — это именованная группа, а \k<word> — обратная ссылка на неё. После успешного совпадения захваченный текст также доступен через объект match.groups. Именованные группы и \k<name> работают во всех современных браузерах и актуальных версиях Node.js без каких-либо флагов.
Повторное использование захватов в replace()
Наиболее распространённое повседневное применение обратных ссылок — не внутри паттерна, а в строке замены String.prototype.replace(). Там захваченный текст указывается через $1, $2, … (или $<name> для именованных групп).
Удобный пример: схлопывание случайно удвоенного слова до одного:
Обратите внимание на различие: \1 (обратный слеш) используется внутри паттерна, а $1 (знак доллара) — в строке замены. Путаница между ними — частый источник ошибок.
Подводный камень: неучаствующие группы
Обратная ссылка на группу, которая не участвовала в совпадении, ведёт себя особым образом. Если группа ни разу не совпала (например, она находилась внутри неиспользованной альтернативы), её захваченное значение равно undefined, и в JavaScript обратная ссылка тогда совпадает с пустой строкой — она успешно срабатывает, ничего не потребляя.
Это легко упустить из виду: можно ожидать, что \1 завершится неудачей, если группа не совпала, но вместо этого она тихо совпадает с пустой строкой. Тщательно структурируйте альтернации, если вам важно, что группа всегда что-то захватила.
Практический пример: парные кавычки
Практический паттерн, который требует обратной ссылки — поиск строки в кавычках, где закрывающая кавычка должна быть тем же символом, что и открывающая: "..." и '...' допустимы, а "...' — нет.
Группа (['"]) захватывает тот символ кавычки, которым открылась строка, а \1 требует, чтобы закрытие было этим же символом. Обычное выражение ["'].*?["'] не может это обеспечить — оно спокойно сопоставило бы "...'. В этом и состоит разница между опережающей/ретроспективной проверкой (которая только утверждает) и обратной ссылкой (которая снова сопоставляет захваченный текст).
Заключение
Используйте обратную ссылку всякий раз, когда более поздняя часть совпадения должна быть равна тексту, найденному ранее — удвоенные слова, парные кавычки, теги с одинаковым именем или правила «соседние символы должны различаться». Помните три главных правила:
- Нумерованные группы считаются по их открывающей
(, начиная с\1; всё совпадение — это группа 0. - Используйте
\1/\k<name>внутри паттерна, а$1/$<name>— вreplace(). - Неучаствующая группа делает так, что её обратная ссылка совпадает с пустой строкой — следите за своими альтернациями.
Для изучения основ обратитесь к разделам Захватывающие группы, Символьные классы и Опережающая и ретроспективная проверка.