Menu
Русский

Регулярные выражения в JavaScript: test, match, replace

Разбираемся, как работают регулярные выражения в JavaScript: два способа создать паттерн, методы test, match и replace, флаги и группы захвата — всё на живых примерах.

Регулярные выражения в JavaScript: шаблоны для поиска по тексту

Регулярные выражения (или просто regex) описывают форму строки: «четыре цифры подряд», «слово с запятой в конце», «что-то похожее на email». В JavaScript регулярку можно создать двумя способами:

index.js
Output
Click Run to see the output here.

Литеральная запись — со слэшами вокруг шаблона и флагами после закрывающего слэша — это то, что вы будете использовать чаще всего. А вот new RegExp(...) пригодится, когда сам шаблон динамический: например, собирается из пользовательского ввода или переменной.

Буква i в конце — это флаг. i означает поиск без учёта регистра. Про флаги расскажу чуть ниже.

test: есть ли совпадение?

Самый простой вопрос, который можно задать регулярке — «а есть ли в строке совпадение?». Для этого и нужен test:

index.js
Output
Click Run to see the output here.

\d — это «любая цифра». Метод test возвращает строго true или false, больше ничего. Если вам нужен ответ в духе «да/нет» — проверить поле формы, отфильтровать массив — test подойдёт как нельзя лучше.

Метод match: достаём найденный текст

Когда нужен сам кусок текста, который совпал, пригодится метод строки match:

index.js
Output
Click Run to see the output here.

Без флага g метод match возвращает массив, где лежит первое совпадение вместе с метаданными (index, input). С флагом g на выходе — обычный массив строк со всеми найденными совпадениями. Если же ничего не нашлось, вернётся null, а не пустой массив — так что не забудьте это проверить:

index.js
Output
Click Run to see the output here.

Флаги меняют поведение паттерна

Флаги ставятся после закрывающего слэша и управляют тем, как работает поиск. Вот те, что пригождаются чаще всего:

  • g — глобальный поиск: находит все совпадения, а не только первое.
  • i — без учёта регистра.
  • m — многострочный режим: ^ и $ совпадают с началом и концом каждой строки, а не всего текста.
  • s — режим dotall: точка . начинает матчить и переводы строк.
  • u — поддержка Unicode, без неё многие эмодзи и не-ASCII символы в паттернах работать не будут.
index.js
Output
Click Run to see the output here.

Без флага m символ ^ цепляется только к самому началу строки. А с флагом m — к началу каждой строки, поэтому в выдачу попадут и Roses, и Violets.

Классы символов и квантификаторы

Кирпичики, из которых собирается большинство regex-шаблонов в JavaScript:

  • \d — цифра, \w — символ слова (буква, цифра или подчёркивание), \s — пробельный символ.
  • [abc] — один из символов a, b или c. [^abc] — что угодно, кроме перечисленного. [a-z] — диапазон.
  • . — любой символ, кроме перевода строки.
  • * — ноль или больше, + — один или больше, ? — ноль или один.
  • {3} — ровно три, {2,5} — от двух до пяти, {2,} — два и больше.
  • ^ — начало, $ — конец.

Собираем всё вместе:

index.js
Output
Click Run to see the output here.

\b — это граница слова, невидимая черта между символом слова и символом не-слова. Пригождается, когда нужно искать «слово целиком».

Группы захвата в regex: запоминаем части совпадения

Круглые скобки создают группу и запоминают то, что в неё попало. Методы exec и match возвращают эти захваты вместе с самим совпадением:

index.js
Output
Click Run to see the output here.

Индекс 0 — это полное совпадение, а каждая группа захвата идёт после него по своему индексу. Считать группы по номерам становится неудобно, как только их больше двух, поэтому лучше давать им имена:

index.js
Output
Click Run to see the output here.

Именованные группы захвата делают код на стороне вызова понятнее и не ломаются, если порядок групп в шаблоне поменяется.

replace: переписываем найденный текст

Метод replace принимает шаблон и замену. В роли замены может выступать как строка, так и функция:

index.js
Output
Click Run to see the output here.

Без флага g заменится только первое совпадение. Классическая ошибка — забыть этот флаг, а потом ломать голову, почему второй email всё ещё выглядит криво.

В строке замены можно использовать обратные ссылки. $1, $2 и так далее ссылаются на группы захвата, а $<имя> — на именованные группы:

index.js
Output
Click Run to see the output here.

Если задача сложнее обычной замены, передавайте функцию. На вход она получает само совпадение и захваченные группы:

index.js
Output
Click Run to see the output here.

Подчёркивание — это полное совпадение (оно нам не нужно), а n — первая группа захвата. Связка «регулярка + функция-заменитель» закрывает большинство реальных задач по обработке текста.

matchAll: все совпадения вместе с группами

String.prototype.matchAll возвращает итератор по всем совпадениям вместе с их группами захвата — то, что обычный match с флагом g сделать не умеет:

index.js
Output
Click Run to see the output here.

matchAll работает только с флагом g. Без него получите TypeError. Если нужен произвольный доступ к элементам, а не перебор в цикле, разверните результат в массив: [...text.matchAll(email)].

Экранирование специальных символов

Символы . * + ? ( ) [ ] { } | \ ^ $ имеют особое значение в регулярных выражениях. Чтобы сопоставить их буквально, экранируйте обратным слэшем:

index.js
Output
Click Run to see the output here.

Вариант без экранирования спокойно подхватит examplexcom, потому что . означает «любой символ». Такой баг встречается сплошь и рядом — и он молчаливый. Если регулярка цепляет лишнее, первым делом ищите неэкранированную ..

Когда вы собираете паттерн из пользовательского ввода, его обязательно нужно экранировать, иначе пользователь сможет подсунуть свой regex-синтаксис:

index.js
Output
Click Run to see the output here.

$& в строке замены — это короткая запись для «всего совпадения».

Опережающие и ретроспективные проверки в regex

Иногда нужно найти совпадение только в том случае, если после него (или перед ним) идёт что-то определённое, — но при этом само это «что-то» в результат не включать. Ровно для таких ситуаций и придуманы lookaround-конструкции:

index.js
Output
Click Run to see the output here.
  • (?= ...) — позитивный lookahead: «за чем следует».
  • (?<= ...) — позитивный lookbehind: «чему предшествует».
  • (?! ...) и (?<! ...) — их негативные варианты.

Lookaround'ы не «съедают» символы, поэтому то, на что мы «подсматриваем», остаётся доступным для следующей части паттерна.

Пара слов о валидации email

Этот вопрос всплывает постоянно: «дайте мне regex, который валидирует email». Честный ответ — не надо. Настоящая грамматика email'ов — это адский зверь, и любое регулярное выражение, достаточно короткое, чтобы его можно было прочитать, в чём-нибудь да ошибётся. Для валидации формы вполне сойдёт прагматичный паттерн:

index.js
Output
Click Run to see the output here.

Читается это так: «несколько символов, которые не являются пробелами и не @, потом @, снова то же самое, точка, и опять то же самое». Такой шаблон ловит очевидные опечатки, но не претендует на соответствие RFC 5322. Если нужна настоящая проверка — отправьте письмо с подтверждением.

Типичные подводные камни

Несколько граблей, на которые стоит наступить в уме, а не в проде:

  • Забыли флаг g при replace или matchAll. В первом случае заменится только одно совпадение, во втором прилетит TypeError.
  • Состояние lastIndex у глобальных регулярок. Регулярное выражение с флагом g или y запоминает, где остановилось, между вызовами test/exec. Не переиспользуйте один и тот же объект на несвязанных строках — создавайте новый либо берите matchAll.
  • Неэкранированные точки и слэши в динамических шаблонах. Всегда экранируйте пользовательский ввод, прежде чем пихать его в new RegExp(...).
  • Катастрофический бэктрекинг. Вложенные квантификаторы вроде (a+)+ на «злой» входной строке способны намертво повесить вкладку. Если регулярка тормозит — упрощайте.

Дальше: даты и время

Регулярки отвечают за форму текста, но в реальных данных ещё есть временные метки — их нужно парсить, форматировать и складывать. На следующей странице разберём Date, Intl.DateTimeFormat и ту самую ментальную модель, благодаря которой баги с часовыми поясами обходят вас стороной.

Часто задаваемые вопросы

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

Есть два способа. Литерал через слэши: /hello/i. И конструктор, который принимает строку: new RegExp('hello', 'i'). Литерал удобен, когда паттерн фиксированный, а конструктор пригодится, если нужно собрать выражение из переменной на лету.

Чем отличаются test, match и exec?

regex.test(str) возвращает булево — самый быстрый вариант, когда нужно просто узнать, есть совпадение или нет. str.match(regex) отдаёт массив совпадений (или null). regex.exec(str) возвращает совпадения по одному, вместе с группами захвата, и при флаге g запоминает позицию между вызовами через lastIndex.

Как заменить все вхождения через regex?

Нужен флаг g: str.replace(/foo/g, 'bar'). Без g заменится только первое совпадение. Ещё есть str.replaceAll(/foo/g, 'bar'), но учтите: если в replaceAll передаётся регулярка, флаг g обязателен — иначе будет ошибка.

Что такое группы захвата в регулярных выражениях?

Это скобки в паттерне, которые запоминают то, что в них попало. Например, /(\d{4})-(\d{2})/.exec('2024-11') вернёт массив, где под индексом 1 лежит '2024', а под индексом 2 — '11'. Группы можно именовать: (?<year>\d{4}), и тогда доступ будет через match.groups.year.

Учитесь программировать с Coddy

НАЧАТЬ