Регулярные выражения помогают разработчикам быстрее находить и анализировать информацию. Благодаря регуляркам можно не только эффективнее решать задачи, но и писать код, который будет лучше работать. Причём не стоит использовать этот метод везде: иногда он только усложняет жизнь.

Давайте разберёмся, что такое регулярные выражения и зачем они нужны.

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

Регулярные выражения — это формальный язык поиска подстроки в строке. Они поддерживаются многими программами: редакторами, системными утилитами, базами данных. Но особенно хорошо возможности этого инструмента раскрываются в языках программирования, в том числе в 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)


  1. Expany
    25.02.2022 12:27
    +3

    Если какую-либо задачу можно решить не прибегая к регуляркам, лучше так и поступить.
    Тоже касается ситуаций, когда регулярка сложнее чем например #start(.*)end#


    1. Alexandroppolus
      25.02.2022 15:23
      +8

      Любую задачу можно решить без регексов - конечным автоматом, или унылой свалкой ифов и циклов. Но с регами зачастую выглядит проще и лаконичнее.


      1. a-tk
        25.02.2022 15:56
        +9

        Собственно, регулярные выражения и есть компактная форма описания таких правил.

        Проблема в том, что если попытаться решить что-то некорректной регуляркой, то это ничем не отличается от решения задачи некорректной лапшой реализации конечного автомата со свалкой ифов и циклов.


        1. Hisoka
          25.02.2022 16:41
          +2

          Да, только лапшой это может быть ещё медленнее сделано.


          1. a-tk
            28.02.2022 08:10

            Я бы сказал скорее всего будет сделано намного медленней.


  1. shark14
    25.02.2022 12:31
    +3

    Странно, что не упомянули про известную уязвимость регулярных выражений — ReDoS.

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


    1. evil_me
      25.02.2022 12:41
      +5

      Ну это, в принципе, как с любыми пользовательскими данными, которые нужно экранировать, чтобы случайно всё не сломалось.


      1. Alexandroppolus
        26.02.2022 07:03
        +2

        Тут не совсем про экранирование, а скорее про плохие регексы (которые всегда можно переписать по нормальному). Тема хорошо раскрыта в учебнике


  1. uldashev
    25.02.2022 12:59
    +3

    email validation

    Шаблон для валидации email согласно стандарту. Вы все еще хотите связываться с регулярками?


    1. polearnik
      25.02.2022 14:16

      как то слишком сложно чтоб проверить что есть @ и что точка идет после собачки


      1. uldashev
        25.02.2022 14:58
        +1

        blabla@????.com - корректно? Собака есть, точка есть. А как такая строка обрабатываться будет: "bla1@gmail.com,bla2@gmail.com,...,bla1000@gmail.com", сервис отправит 1000 подтверждений?

        Есть RFC2822, приведенное выше регулярное выражение написано в соответствии с ним.


        1. Ndochp
          25.02.2022 15:16
          +6

          blabla@????.com
          А разве не корректно? вроде юникод в домене может быть.


          1. uldashev
            26.02.2022 20:44

            del


        1. martin_wanderer
          25.02.2022 15:37

          А в соответствии ли? Насколько я помню, грамматика RFC2822 в принципе нерегулярна. И, следовательно, не может быть полностью описана никаким регулярным выражением.


          1. uldashev
            25.02.2022 15:59
            +1

            Неа, не соответствует, время для редактирования вышло, не поправить, соответствует старому стандарту rfc822. Но про то и речь, что регулярки на учебных примерах смотрятся красиво, а на стандартах или реальных задачах получается как в старой поговорке про то, что если у Вас есть проблема и вы решаете её через регулярки - поздравляем, теперь у Вас две проблемы.

            Хотя расширение chrome regex search иногда бывает полезным.


            1. martin_wanderer
              25.02.2022 17:23
              +2

              Знаете, я очень люблю регулярные выражения. И при этом полностью согласен с поговоркой) Просто у каждого инструмента есть свои границы применимости. Когда приходит аналитик, и просит отобрать все идентификаторы, где есть буквы, один разработчик уточняет, русские или английские (забыв про турецкие и весь остальной юникод), после чего пишет какую-то жуть, а другой спрашивает "есть буквы, или есть не-цифры". После чего использует супер-сложную регулярку `\D`


        1. a-tk
          28.02.2022 08:12

          bla+0001@gmail.com тоже корректно (как и любая буквенно-цифровая последовательность после символа +)


      1. 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]>" тоже (точки после собаки нету)


      1. skozharinov
        25.02.2022 16:03

        точка может быть и до собачки


        1. a-tk
          28.02.2022 08:14

          Расскажите об этом упырям из ГМыла. Они не считают учётки, отличающиеся только точками, различными.


          1. Ndochp
            28.02.2022 09:23

            Зато считают адрес валидным. То есть можно считать, что не только все адреса с тегами после + ваши, но и все адреса с точками в любом месте тоже ваши.


    1. mSnus
      25.02.2022 15:04

      Email сложнее, чем мы привыкли его видеть)

      Но регуляркам не хватает встроенных функций, тогда все решилось бы проще


    1. Spearance
      25.02.2022 22:31
      +4

      const checkEmail = (email) => {
        let input = document.createElement(`INPUT`);
        input.type = `email`
        input.value = email;
      
        return input.checkValidity();
      };

      есть вариант без простынки с регуляркой :)

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

      Как минимум \r тоже должна быть \r? Далеко не все на винде :)


      1. victor-homyakov
        28.02.2022 00:00
        +1

        есть вариант без простынки с регуляркой

        Таким образом мы перекладываем проблему валидации на реализацию в какой-то версии какого-то браузера. При этом реализации от версии к версии и от движка к движку могут меняться (могут появляться или исправляться баги).


    1. ihouser
      26.02.2022 00:05
      +2

      Необязательно ставить задачу ребром и использовать или регулярку или код. Можно разбить проверку на подзадачи и комбинировать код с регуляркой. И там и там есть преимущества.

      Тогда весь код становится и короче и понятнее.


    1. Shaco
      26.02.2022 12:55
      +3

      Этот шаблон пытается решить выдуманную задачу, которая в реальном мире бывает один раз на миллион. Никому не интересно, «соответствует введённая строка стандарту или нет», интересно только «могу я использовать её в качестве адреса, чтобы связаться с пользователем или нет». Однозначно и утвердительно ответить на этот вопрос на фронте невозможно по множеству причин, начиная от неспособности вашего почтового сервера отправить письмо на валидный адрес и заканчивая опечаткой. Поэтому лучшее, что вы можете сделать — считать указанную строку адресом и отправить по ней письмо со ссылкой/кодом подтверждения, что все и делают. Но это делается бекендом.

      А задачей фронтенда в этом случае — уберечь пользователя от банальной ошибки, вроде попытки ввести логин вместо адреса. Проверить @ и наличие после неё точки для этой цели достаточно, регулярка с этой задачей справится легко и элегантно.


  1. a-tk
    25.02.2022 13:00

    следом за ним может быть (а может и не быть) пробельный символ \s*;

    Может быть, а может не быть - это квантификатор ?, эквивалентный {0,1}. То, что Вы написали - это возможно пустая последовательность из произвольного количества пробелов, то есть {0,}.

    А как насчёт того, что в строке могут быть до и после любые символы?


    1. Feizerr Автор
      25.02.2022 17:05

      Задача — валидировать номер паспорта РФ, у которого достаточно понятная структура, состоящая из серии и номера: 99 99 №999999. Кроме серии и номера могут присутствовать пробелы и символ номера. Звёздочка помогает нам поймать любое количество пробельных символов между цифрами, если они есть (может быть, а может не быть). Символ № тоже необязателен, и паттерн будет верным даже при таком наборе: 9999999999. А вот любые знаки до и после уже не совпадают с шаблоном паспорта РФ, поэтому поле будет считаться невалидным.


  1. Mapaxa864
    25.02.2022 17:00

    Слева от позиции, в которой начинается «е», не должно быть русских букв. Так мы исключаем слова с окончанием «ее» через ретроспективную проверку.

    А чем это отличается от пробела, о котором ранее в самой же статье и шла речь?


    1. Feizerr Автор
      25.02.2022 18:57

      Вы правы, мы могли бы воспользоваться позитивной ретроспективной проверкой и проверить на наличие пробельного символа (?<=\s) вместо (?<![а-яё]), но тогда выражение не совпало бы в начале строки.


      1. Mapaxa864
        25.02.2022 19:05

        Так ведь в регулярках как правило есть метасимвол начала строки на этот случай.


      1. wadeg
        25.02.2022 21:36
        +1

        Вообще-то гораздо аккуратнее и правильнее тут использовать \b, он убережет и от других проблем — например, разработчик не учел возможность знаков препинания до и после захватываемой строки.


        1. Spearance
          25.02.2022 22:05

          в JavaScript \b работает только с латинским алфавитом


          1. wadeg
            25.02.2022 22:40

            О как. Это действительно неустранимо в js или там какие-то настройки покрутить надо? А всякие альфанумерики etc там тоже только с латиницей работают?


            1. Spearance
              26.02.2022 07:26
              +1

              увы, нет такой настройки, а с альфанумериками всё ок


  1. Keeper1
    25.02.2022 19:47
    -3

    Слабаки! Я на регулярках разбор HTML делал.


    1. gdt
      26.02.2022 07:33
      +3

      1. Keeper1
        26.02.2022 11:32
        +4

        Тогда я не знал, что нельзя. И распарсил. : )


        1. gdt
          26.02.2022 14:46

          Ну какое-то подмножество можно конечно



  1. agalakhov
    26.02.2022 18:39
    +1

    Главная проблема использования регулярных выражений в том, что им соответствуют только регулярные грамматики. Но постоянно встречаются люди, которые пытаются парсить ими как минимум контекстно-свободные, а потом удивляются, что что-то пошло не так.


  1. Alexandroppolus
    26.02.2022 21:53

    С регами бывают ещё какие-то странные проблемы, когда выскакивает переполнение стека.

    Например, сделаем длинную строку из латинских 'a':

    const longStr = 'a'.repeat(1e7);

    Вызов /a+/.test(longStr) отрабатывает нормально и возвращает true, а вызов /(a)+/.test(longStr) приводит к ошибке. С более короткими строками отрабатывает без проблем. Конкретно на моем макбуке ошибка проявляется в Хроме и Node 14, если длина строки 3355430 и больше


    1. Spearance
      27.02.2022 12:58

      могу предположить что слишком большое количество сохраненных состояний срабатывает как зацикливание, по крайней мере FireFox ровно об этом и говорит. Если отключить сохранение /(?:a)+/.test(longStr) всё прекрасно работает



  1. AnthonyMikh
    27.02.2022 18:07

    Правда ли, что от регулярок у разработчиков одни проблемы

    <shameless_plug>

    Да

    </shameless_plug>

    Другое дело, что рабочие альтернативы в Javascript могут иметь существенно более низкую производительность, чем непрозрачные регулярки, реализованные в движке в нативном коде, так что для Javascript (а также Ruby, Python, PHP) может иметь смысл впихивать парсеры в прокрустово ложе регулярок.