DanLevy.net

Тест: Мастерство регулярных выражений

Сможете приручить дикий RegEx?

Готовы схватиться с регулярными выражениями? 🤼‍♂️

Проверьте свои знания RegEx с вопросами, охватывающими базовые шаблоны, квантификаторы, группы и эти хитрые look‑around‑утверждения. От простого сопоставления строк до сложной валидации шаблонов — сможете ли вы найти правильное регулярное выражение?

Что совпадает?

'cat CAT Cat'.match(/cat/g)

Этот шаблон использует g, но не i:

  • g ищет все совпадения
  • Без i сопоставление чувствительно к регистру

Без флага i совпадает только нижний регистр “cat”.

Это особенно полезно при работе с вводом пользователя или HTML, где регистр может различаться.

Узнать больше о флагах RegExp

Что вернёт этот код?

const words = ['cat', 'hat', 'what', 'bat'];
words.filter(word => word.match(/^[ch]at/))

Шаблон /^[ch]at/ сопоставляет строки, которые:

  • Начинаются (^) с символа ‘c’ или ‘h’ (это значит, что [ch] — класс символов, соответствующий одному символу)
  • За ними следует буквальное ‘at’

Поэтому только “cat” и “hat” подходят под этот шаблон. Метод filter() оставляет только подходящие элементы.

Узнать больше о классах символов на MDN

Что это будет находить?

'<div>Hello</div><div>World</div>'.match(/<div>.*?<\/div>/g)

Шаблон /<div>.*?<\/div>/g использует нежадное сопоставление с *?, что означает:

  • Совпадение <div>
  • Совпадение любого символа (.*), но как можно меньше (?)
  • До нахождения </div>
  • Флаг g заставляет искать все вхождения

Без ? жадный .* захватил бы всё от первого <div> до последнего </div>, получив одно большое совпадение. С ? он сопоставляет каждую пару отдельно.

Узнать больше о жадном и ленивом сопоставлении

Что это вернёт?

'hello\nworld'.match(/\w+/g)

Шаблон \w+ соответствует одному или более символьным символам слова. Даже если в строке есть перевод строки, \w совпадает с:

  • Буквами (a-z, A-Z)
  • Цифрами (0-9)
  • Подчёркиванием (_)

Таким образом, перевод строки действует как граница слова, и мы получаем два совпадения. Если бы мы использовали .*, он бы по умолчанию не совпадал с переводом строки (нужен флаг s).

Узнать больше о метасимволах

Что это будет находить?

'$100 and €50'.match(/\d+(?=[\$€])/g)

Эта схема ничего не найдёт, потому что просмотр вперёд направлен назад! Если вам нужны цифры, предшествующие $ или , используйте просмотр назад: /(?<=[\$€])\d+/g.

Просмотры вперёд проверяют, что идёт после текущей позиции. Записанный шаблон ищет:

  • Одна или более цифр (\d+)
  • За которыми следует ((?=...)) либо $ либо € ([\$€])

Поскольку нет чисел, за которыми идут символы валют (они идут перед ними), совпадений нет.

Узнайте больше о просмотре вперёд

Что будет совпадать?

'cat cats category'.match(/\bcat\b/g)

\b обозначает границу слова. Она совпадает:

  • Между символом слова и не-символом слова
  • В начале или конце строки, если рядом есть символ слова

Поэтому /\bcat\b/ находит “cat” только как отдельное слово, а не как часть другого слова.

  • ✅ “cat” (окружено пробелами)
  • ❌ “cats” (после “cat” нет границы слова)
  • ❌ “category” (после “cat” нет границы слова)

Узнайте больше о границах слов

Каков вывод?

'banana'.match(/a/g)

Флаг g (global, глобальный) меняет поведение match():

  • Без g: возвращает первое совпадение вместе с группами захвата
  • С g: возвращает массив всех найденных строк

В этом случае выражение находит все вхождения “a” в “banana”.

Примечание: если вам нужны и все совпадения, и группы захвата, используйте matchAll() или метод exec() в цикле.

Узнайте больше о глобальном флаге

Что соответствует этому шаблону?

'abc123 def456'.match(/(?<!abc)\d+/g)

Отрицательный обратный просмотр (?<!abc) гарантирует, что цифры не предшествуют “abc”:

  • ❌ “123” (предшествует “abc”)
  • ✅ “23” (предшествует “abc1”)
  • ✅ “456” (предшествует “def”)

JavaScript поддерживает утверждения обратного просмотра в современных движках. Этот пример использует фиксированную длину обратного просмотра: abc всегда три символа. Обратный просмотр переменной длины — более сложный, зависящий от движка случай.

Примечание: поддержка обратного просмотра в JavaScript относительно недавняя. Проверьте совместимость браузеров, если нужно поддерживать старые браузеры.

Что это вернёт?

'2029-12-31'.match(/(\d{4})-(\d{2})-(\d{2})/).slice(1)

Шаблон использует три группы захвата:

  1. (\d{4}) захватывает год
  2. (\d{2}) захватывает месяц
  3. (\d{2}) захватывает день

match() без флага g возвращает:

  • Индекс 0: Полное совпадение
  • Индекс 1+: Группы захвата

slice(1) — распространённый приём, чтобы получить только группы захвата.

Узнайте больше о группах и захвате

Каков будет результат?

"123aBc".match(/^\d+(?![a-z])/ig)

Отрицательное просмотр вперёд (?![a-z]) гарантирует отсутствие строчных букв после цифр. Поскольку в части “3aBc” после цифр есть строчная буква, эта часть не совпадает. Поэтому совпадает только начало “12”.

Узнайте больше об отрицательном просмотре вперёд

Что возвращается?

'a,b,c'.split(/(?<=,)/)

Шаблон /(?<=,)/ — это look‑behind, который совпадает после запятой:

  • a, (после запятой)
  • b, (после запятой)
  • c (нет запятой после)

Look‑behind не поглощает запятую, поэтому запятая остаётся прикреплённой к предыдущему сегменту в результате split.

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

Узнать больше о look-behind утверждениях

Что совпадает?

'$100'.match(/$\d+/)

Специальные символы нужно экранировать с помощью \\, чтобы сопоставлять их буквально:

  • $ — специальный символ (конец строки)
  • Чтобы сопоставить буквальный знак доллара, экранируйте его: \\$

Общие символы, требующие экранирования:

. * + ? ^ $ [ ] \ ( ) { } |

Без экранирования многие специальные символы имеют значения в regex, которые могут не соответствовать вашим ожиданиям.

Узнайте больше об экранировании специальных символов

Что будет найдено?

'$100'.match(/(?<=\$)\d+/)

Положительный просмотр назад (?<=\$) гарантирует, что цифры идут после знака доллара:

  • (?<=\$): просмотр назад для знака доллара
  • \d+: соответствует одной или более цифрам

Утверждения просмотра назад не поглощают символы; они лишь проверяют, что стоит перед ними. Это полезно, когда нужно найти что‑то, опираясь на предшествующий контекст, не включая его в результат.

Узнайте больше о утверждениях просмотра назад

Что будет найдено?

'<b>bold</b>'.match(/<b>(.*?)<\/b>/).slice(1)

Шаблон использует ленивое совпадение с *?:

  • <b>: Совпадает открывающий тег
  • (.*?): Захватывает любые символы (лениво)
  • </b>: Совпадает закрывающий тег

? после * делает квантификатор ленивым, он захватывает как можно меньше символов. Без ? он был бы жадным и захватывал бы как можно больше.

slice(1) возвращает только захваченную группу.

Узнайте больше о жадных и ленивых квантификаторах

Что совпадает?

'😀 🙂'.match(/\p{Emoji}/gu)

u‑флаг включает:

  • Unicode‑свойства (\p{...})
  • Правильную обработку суррогатных пар

Без u эмодзи и другие Unicode‑символы могут не совпадать корректно. Шаблон \p{Emoji} соответствует символам с Unicode‑свойством Emoji. В этой строке это два эмодзи‑пиктографа.

Примечание: Unicode‑свойства требуют флага u.

Узнать больше о Unicode‑режиме

Извиняюсь заранее! 😈
Какой пароль соответствует этому шаблону?

/^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$/

Не пишите ничего подобного в продакшене! 😅

Этот шаблон использует несколько позитивных look-ahead, чтобы обеспечить:

  • хотя бы одну заглавную букву: (?=.*[A-Z])
  • хотя бы одну строчную букву: (?=.*[a-z])
  • хотя бы одну цифру: (?=.*\d)
  • хотя бы один специальный символ: (?=.*[!@#$%^&*])
  • минимальную длину 8: .{8,}

Look-ahead идеальны для проверки пароля, потому что они могут проверять несколько условий, не потребляя символы.

Узнайте больше о шаблонах проверки паролей

Как у вас получилось? 🧐

Регулярные выражения могут быть диким зверем, но они невероятно мощные, как только вы освоите их (и весь новый синтаксис). Продолжайте практиковаться, и вы станете мастером RegEx в кратчайшие сроки! 🧙‍♂️

Нужен перерыв после всей этой RegEx?
Пфф, помните: перерыв после навыков!

Загляните в мой зал, чтобы решить ещё несколько задач! 💪