Регулярные выражения помогают разработчикам быстрее находить и анализировать информацию. Благодаря регуляркам можно не только эффективнее решать задачи, но и писать код, который будет лучше работать. Причём не стоит использовать этот метод везде: иногда он только усложняет жизнь.
Давайте разберёмся, что такое регулярные выражения и зачем они нужны.
Что такое регулярные выражения?
Регулярные выражения — это формальный язык поиска подстроки в строке. Они поддерживаются многими программами: редакторами, системными утилитами, базами данных. Но особенно хорошо возможности этого инструмента раскрываются в языках программирования, в том числе в JavaScript.
Рассмотрим простой пример, чтобы понять, зачем нужны регулярные выражения. Допустим, перед нами стоит задача — найти и заменить местоимение 'ее' на 'его' в строке 'Быстрее всего мы догоним ее на машине'.
Самое очевидное решение — использовать прямую замену, применив встроенную в JavaScript функцию:
'Быстрее всего мы догоним ее на машине'.replace('ее', 'его');
Однако 'ее'
также является окончанием слова 'Быстрее'
, а .replace()
заменит первое вхождение подстроки. В итоге мы получим ожидаемо неверный результат: 'Быстрего всего мы догоним ее на машине'
. Поэтому необходимо проверить строку на наличие символа, стоящего перед 'ее'
: если это пробел, можно делать замену.
В задаче могут появиться и другие условия. Например, мы не знаем, в каком регистре написаны слова (ее, Ее или ЕЕ) и используется ли буква «ё». Если добавить их в функцию, она станет слишком большой и сложной. Это может привести к другим ошибкам.
Регулярные выражения упрощают решение таких задач. Они помогают последовательно перебрать все символы и произвести замену в нужном месте без написания длинных и сложных функций. Например, в нашем примере можно было заменить «ее» на «его» с помощью регулярного выражения:
'Быстрее всего мы догоним ее на машине'.replace(/(?<![а-яё])е[её]/ig, 'его’);
Пожалуй, это самый простой вариант замены, хотя для новичков и он может показаться сложным. Как его правильно прочитать:
Ищем букву «е», справа от которой стоят «е» или «ё».
Слева от позиции, в которой начинается «е», не должно быть русских букв. Так мы исключаем слова с окончанием «ее» через ретроспективную проверку.
Модификатор
i
ищет совпадения в любом регистре, модификаторg
ищет глобально все совпадения.
Востребованы ли регулярные выражения?
Осенью 2021 года мы запустили масштабное исследование, чтобы понять, какие навыки востребованы и какие знания нужны разработчикам для трудоустройства на позицию джуна, мидла или сеньора. Чтобы получить максимально точные результаты, мы изучили 1 000 вакансий, а также провели интервью с тимлидами, эйчарами, наставниками и выпускниками HTML Academy.
По исследованию мы поняли, что чем выше уровень, тем востребованнее для разработчика фундаментальные знания. Регулярные выражения можно отнести к базовой технологии, которую работодатели не выделяют в вакансиях отдельно, но которая очень полезна. Ведь если фронтендер работает с текстами или пользовательскими данными, знание регулярных выражений поможет ему быстрее и проще решать поставленные задачи.
Подробнее об исследовании читайте в статье «Я ещё мидл или уже сеньор? И сколько мне должны платить?»
Какие задачи можно решать с помощью регулярных выражений?
Регулярные выражения могут облегчить работу фронтендеру не только при работе с кодом, но и при его написании. Среди типовых задач, в которых регулярные выражения действительно могут пригодиться, можно отметить следующие:
Поиск или замена подстроки в строке с «плавающими» (неизвестными) данными. Самая распространённая задача — найти в тексте ссылки и адреса электронной почты и сделать их кликабельными.
Валидация данных формы и ограничение ввода. Например, валидация номера телефона, электронной почты, данных паспорта гражданина РФ и другой информации.
Получение части строки или формирование новых структур данных из строк. Например, нужно найти количество вхождений ключевых слов в тексте без учёта падежных окончаний, составить из них массив с данными для дальнейшего использования.
Чаще всего фронтенд-разработчики встречаются с регулярными выражениями в задачах, связанных с валидацией данных. Например, проверка паспорта может выглядеть так:
<input type="text" name="passport" placeholder="00 00 №000000" pattern="(\d{2}\s*){2}№?\d{6}">
Что здесь происходит:
сначала мы находим число, состоящее из двух знаков
\d{2}
;следом за ним может быть (а может и не быть) пробельный символ
\s*
;затем снова число, состоящее из двух знаков
{2}
;за ним может последовать
№?
, но необязательно;и затем шесть цифр номера паспорта
\d{6}
.
В контексте поиска и замены текста регулярные выражения используют редко, а в сложных кейсах по работе с текстом — ещё меньше. Но они могут помочь, если задача связана с обработкой текста.
Как регулярные выражения помогают с задачами, которые не касаются написания кода напрямую?
По сути, код — это текст, по которому также можно запускать поиск, выполнять ручные и автоматические замены. При работе над большими проектами такие операции надо проводить очень аккуратно, чтобы не удалить ничего лишнего.
Вот пример использования регулярных выражений для поиска в текстовом редакторе:
Какие типовые задачи решаются регулярными выражениями:
Подготовка и обработка данных. Когда вы выносите предварительные данные в текстовый редактор и готовите их для следующих операций.
Написание кода с большим количеством одинаковых конструкций.
Поиск и гибкая замена в коде. К примеру, с помощью регулярных выражений можно найти и заменить код страны в телефонных номерах:
const data = ['85558345434', '71236452378', '75558755555', '83889068345', '80237862453'];
const result = data.map(item => item.replace(/^8/, '7'));
В этом примере важен знак начала строки ^, ведь без него будет заменён первый символ 8 в любом из номеров телефонов, даже в правильных.
Регулярные выражения вне фронтенда
Регулярные выражения применяются во многих языках программирования, но это не значит, что они везде раскрываются одинаково. В зависимости от языка они могут, например, добавлять разные фичи и работать с разной скоростью.
Если разобраться с регулярными выражениями на примере JavaScript, то обращаться к ним в других языках программирования будет легче. Но изучать нюансы и стандарты внутри каждого языка точно придётся. Если регулярки нужны вам в работе, приходите на курс «Регулярные выражения для фронтендеров».
Когда нет смысла применять регулярные выражения?
В некоторых случаях этот инструмент усложняет реализацию или увеличивает время выполнения кода. Регулярные выражения не нужны, если стандартные функции JavaScript справляются с задачей сами. Вот ещё пара ситуаций:
Структура содержимого данных хорошо описана, легко поддаётся разбору, можно применить нативные методы работы со строкой.
Предполагается работа с тегами, правка атрибутов или содержимого.
Разумеется, это общие примеры, и научиться подбирать правильное решение можно только с опытом. Главное, чтобы оно позволяло тратить на задачу меньше ресурсов и повышало производительность.
Выводы
Регулярные выражения — довольно мощный инструмент. Он отлично справляется с задачами, которые сложно решить с помощью нативных методов языка, упрощает работу с кодом и даже его написание. При этом нужно понимать, когда его использовать, чтобы работать быстрее и эффективнее.
Комментарии (45)
shark14
25.02.2022 12:31+3Странно, что не упомянули про известную уязвимость регулярных выражений — ReDoS.
Это работает далеко не для всех регулярок (в основном речь про вложенные символы повторения наподобие
(a+)+
), но многие движки к этому чувствительны.
Это не значит, что от регекспов стоит отказываться, просто при обработке пользовательского ввода (особенно на бекенде) нужно быть осторожным и иметь в виду такие подводные камни.evil_me
25.02.2022 12:41+5Ну это, в принципе, как с любыми пользовательскими данными, которые нужно экранировать, чтобы случайно всё не сломалось.
Alexandroppolus
26.02.2022 07:03+2Тут не совсем про экранирование, а скорее про плохие регексы (которые всегда можно переписать по нормальному). Тема хорошо раскрыта в учебнике
uldashev
25.02.2022 12:59+3email validation
Шаблон для валидации email согласно стандарту. Вы все еще хотите связываться с регулярками?
polearnik
25.02.2022 14:16как то слишком сложно чтоб проверить что есть @ и что точка идет после собачки
uldashev
25.02.2022 14:58+1blabla@????.com - корректно? Собака есть, точка есть. А как такая строка обрабатываться будет: "bla1@gmail.com,bla2@gmail.com,...,bla1000@gmail.com", сервис отправит 1000 подтверждений?
Есть RFC2822, приведенное выше регулярное выражение написано в соответствии с ним.
martin_wanderer
25.02.2022 15:37А в соответствии ли? Насколько я помню, грамматика RFC2822 в принципе нерегулярна. И, следовательно, не может быть полностью описана никаким регулярным выражением.
uldashev
25.02.2022 15:59+1Неа, не соответствует, время для редактирования вышло, не поправить, соответствует старому стандарту rfc822. Но про то и речь, что регулярки на учебных примерах смотрятся красиво, а на стандартах или реальных задачах получается как в старой поговорке про то, что если у Вас есть проблема и вы решаете её через регулярки - поздравляем, теперь у Вас две проблемы.
Хотя расширение chrome regex search иногда бывает полезным.
martin_wanderer
25.02.2022 17:23+2Знаете, я очень люблю регулярные выражения. И при этом полностью согласен с поговоркой) Просто у каждого инструмента есть свои границы применимости. Когда приходит аналитик, и просит отобрать все идентификаторы, где есть буквы, один разработчик уточняет, русские или английские (забыв про турецкие и весь остальной юникод), после чего пишет какую-то жуть, а другой спрашивает "есть буквы, или есть не-цифры". После чего использует супер-сложную регулярку `\D`
a-tk
28.02.2022 08:12bla+0001@gmail.com тоже корректно (как и любая буквенно-цифровая последовательность после символа +)
Ndochp
25.02.2022 15:09+2А точно фраза "@sidorov вроде писал что-то про эту задачу." является адресом? При этом "Pete(A wonderful ) chap) <pete(his account)@silly.test(his host)>" — адрес
и "Pete(A wonderful ) chap) <pete(his account)@[IPv6:0:0:1]>" тоже (точки после собаки нету)
mSnus
25.02.2022 15:04Email сложнее, чем мы привыкли его видеть)
Но регуляркам не хватает встроенных функций, тогда все решилось бы проще
Spearance
25.02.2022 22:31+4const checkEmail = (email) => { let input = document.createElement(`INPUT`); input.type = `email` input.value = email; return input.checkValidity(); };
есть вариант без простынки с регуляркой :)
А в той что вы указали начало точно не про email, как будто бессмысленная регулярка для объема, возврат каретки, перевод строки, причем необязательные, за которыми должен обязательно идти один пробел или табулятор и вся скобка тоже необязательна.
Как минимум
\r
тоже должна быть\r?
Далеко не все на винде :)victor-homyakov
28.02.2022 00:00+1есть вариант без простынки с регуляркой
Таким образом мы перекладываем проблему валидации на реализацию в какой-то версии какого-то браузера. При этом реализации от версии к версии и от движка к движку могут меняться (могут появляться или исправляться баги).
ihouser
26.02.2022 00:05+2Необязательно ставить задачу ребром и использовать или регулярку или код. Можно разбить проверку на подзадачи и комбинировать код с регуляркой. И там и там есть преимущества.
Тогда весь код становится и короче и понятнее.
Shaco
26.02.2022 12:55+3Этот шаблон пытается решить выдуманную задачу, которая в реальном мире бывает один раз на миллион. Никому не интересно, «соответствует введённая строка стандарту или нет», интересно только «могу я использовать её в качестве адреса, чтобы связаться с пользователем или нет». Однозначно и утвердительно ответить на этот вопрос на фронте невозможно по множеству причин, начиная от неспособности вашего почтового сервера отправить письмо на валидный адрес и заканчивая опечаткой. Поэтому лучшее, что вы можете сделать — считать указанную строку адресом и отправить по ней письмо со ссылкой/кодом подтверждения, что все и делают. Но это делается бекендом.
А задачей фронтенда в этом случае — уберечь пользователя от банальной ошибки, вроде попытки ввести логин вместо адреса. Проверить @ и наличие после неё точки для этой цели достаточно, регулярка с этой задачей справится легко и элегантно.
a-tk
25.02.2022 13:00следом за ним может быть (а может и не быть) пробельный символ
\s*
;Может быть, а может не быть - это квантификатор
?
, эквивалентный{0,1}
. То, что Вы написали - это возможно пустая последовательность из произвольного количества пробелов, то есть{0,}
.А как насчёт того, что в строке могут быть до и после любые символы?
Feizerr Автор
25.02.2022 17:05Задача — валидировать номер паспорта РФ, у которого достаточно понятная структура, состоящая из серии и номера: 99 99 №999999. Кроме серии и номера могут присутствовать пробелы и символ номера. Звёздочка помогает нам поймать любое количество пробельных символов между цифрами, если они есть (может быть, а может не быть). Символ № тоже необязателен, и паттерн будет верным даже при таком наборе: 9999999999. А вот любые знаки до и после уже не совпадают с шаблоном паспорта РФ, поэтому поле будет считаться невалидным.
Mapaxa864
25.02.2022 17:00Слева от позиции, в которой начинается «е», не должно быть русских букв. Так мы исключаем слова с окончанием «ее» через ретроспективную проверку.
А чем это отличается от пробела, о котором ранее в самой же статье и шла речь?
Feizerr Автор
25.02.2022 18:57Вы правы, мы могли бы воспользоваться позитивной ретроспективной проверкой и проверить на наличие пробельного символа (?<=\s) вместо (?<![а-яё]), но тогда выражение не совпало бы в начале строки.
Mapaxa864
25.02.2022 19:05Так ведь в регулярках как правило есть метасимвол начала строки на этот случай.
agalakhov
26.02.2022 18:39+1Главная проблема использования регулярных выражений в том, что им соответствуют только регулярные грамматики. Но постоянно встречаются люди, которые пытаются парсить ими как минимум контекстно-свободные, а потом удивляются, что что-то пошло не так.
Alexandroppolus
26.02.2022 21:53С регами бывают ещё какие-то странные проблемы, когда выскакивает переполнение стека.
Например, сделаем длинную строку из латинских 'a':
const longStr = 'a'.repeat(1e7);
Вызов /a+/.test(longStr) отрабатывает нормально и возвращает true, а вызов /(a)+/.test(longStr) приводит к ошибке. С более короткими строками отрабатывает без проблем. Конкретно на моем макбуке ошибка проявляется в Хроме и Node 14, если длина строки 3355430 и больше
Spearance
27.02.2022 12:58могу предположить что слишком большое количество сохраненных состояний срабатывает как зацикливание, по крайней мере FireFox ровно об этом и говорит. Если отключить сохранение
/(?:a)+/.test(longStr)
всё прекрасно работает
AnthonyMikh
27.02.2022 18:07Правда ли, что от регулярок у разработчиков одни проблемы
<shameless_plug>
Да
</shameless_plug>
Другое дело, что рабочие альтернативы в Javascript могут иметь существенно более низкую производительность, чем непрозрачные регулярки, реализованные в движке в нативном коде, так что для Javascript (а также Ruby, Python, PHP) может иметь смысл впихивать парсеры в прокрустово ложе регулярок.
Expany
Если какую-либо задачу можно решить не прибегая к регуляркам, лучше так и поступить.
Тоже касается ситуаций, когда регулярка сложнее чем например
#start(.*)end#
Alexandroppolus
Любую задачу можно решить без регексов - конечным автоматом, или унылой свалкой ифов и циклов. Но с регами зачастую выглядит проще и лаконичнее.
a-tk
Собственно, регулярные выражения и есть компактная форма описания таких правил.
Проблема в том, что если попытаться решить что-то некорректной регуляркой, то это ничем не отличается от решения задачи некорректной лапшой реализации конечного автомата со свалкой ифов и циклов.
Hisoka
Да, только лапшой это может быть ещё медленнее сделано.
a-tk
Я бы сказал скорее всего будет сделано намного медленней.