Хотите заказать веб-сайт? Связаться с нами

Регулярные выражения JS: полный гайд

Современная веб-разработка редко обходится без обработки текста. Будь то валидация формы обратной связи, парсинг URL или подсветка синтаксиса в редакторе — разработчику постоянно приходится искать определенные последовательности символов. Здесь на помощь приходят регулярные выражения JS.

Регулярное выражение (или regexp) — это формальный язык поиска и осуществления манипуляций с подстроками в тексте. В JavaScript этот механизм представлен отдельным объектом RegExp. Освоив его, вы сможете писать более лаконичный и эффективный код для задач, которые без regexp потребовали бы десятков строк условных конструкций.

Регулярные выражения JS: полный гайд

Синтаксис создания регулярного выражения

В JavaScript существует два способа создать регулярное выражение: литеральная запись (используется чаще всего) и конструктор объекта. Синтаксис создания регулярного выражения предполагает использование слешей или вызов конструктора.

Пример 1

Литеральная запись: шаблон заключается в слеши (/).

const patternForEmail = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

Пример 2

Конструктор RegExp: позволяет создавать выражение динамически из строки.

let searchTag = "div";
let dynamicPattern = new RegExp("<" + searchTag + ">", "gi");

Основная разница: литерал компилируется один раз при загрузке скрипта, тогда как конструктор создает выражение каждый раз во время выполнения, что удобно для динамических шаблонов.

Основные символы и метасимволы

Регулярные выражения состоят из обычных символов (например, букв) и специальных метасимволов, которые задают правила поиска. Основные символы и метасимволы образуют фундамент для построения любых шаблонов.

Таблица ключевых метасимволов

Символ Описание
\. Точка заменяет собой любой одиночный символ, кроме переноса строки.
\d Находит любую цифру (от 0 до 9).
\D Находит любой символ, не являющийся цифрой.
\w Находит любой буквенно-цифровой символ и знак подчеркивания (латиница).
\W Находит любой символ, кроме букв, цифр и подчеркивания.
\s Находит любой пробельный символ (пробел, табуляция, перевод строки).
\S Находит любой непробельный символ.
^ Указывает на начало строки.
$ Указывает на конец строки.

Квантификаторы: повторение последовательностей

Часто нужно найти не один символ, а целую последовательность. Для этого используются квантификаторы. Квантификаторы: повторение последовательностей позволяют указать, сколько раз символ или группа должны повторяться.

Рассмотрим их на практике:

  • {n} — точное количество повторений.
  • {n,} — от n и более повторений.
  • {n,m} — от n до m повторений включительно.
  • * — ноль или более раз (эквивалент {0,}).
  • + — один или более раз (эквивалент {1,}).
  • ? — ноль или один раз (эквивалент {0,1}).

Пример 3

Ищем артикулы товаров в формате «ABC-1234» или «XYZ-7».

let productCodes = ["ABC-1234", "XYZ-7", "TEST-00"];
let findProductCode = /^[A-Z]{3,4}-\d{1,4}$/; // От 3 до 4 букв, дефис, от 1 до 4 цифр

productCodes.forEach(code => {
    console.log(code + ': ' + findProductCode.test(code));
    // Результат: ABC-1234: true, XYZ-7: true, TEST-00: false
});

Работа с наборами символов

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

Набор [abc] найдет либо a, либо b, либо c. Внутри набора можно указывать диапазоны через дефис: [a-z] — все маленькие латинские буквы, [0-9] — цифры. Если нужно исключить символы, используется знак отрицания ^ внутри скобок: [^0-9] означает «все, кроме цифр».

Пример 4

Разрешаем пользователю использовать только определенные спецсимволы.

let userPassword = "MyP@ssw0rd!";
let allowedSymbols = /^[a-zA-Z0-9@!#]+$/; // Разрешены: буквы, цифры и символы @, !, #

if (allowedSymbols.test(userPassword)) {
    console.log("Надежный состав символов");
} else {
    console.log("Использованы запрещенные символы");
}
Из ноутбука появляются летающие светящиеся экраны

Оживи свой сайт. Освой JavaScript!

Статичная верстка — это только скелет. Наш онлайн-курс "JavaScript с нуля до профи" даст твоим страницам мышцы и нервы. Научись создавать слайдеры, формы, интерактивные карты и получать данные с сервера.

От теории — к реальным скриптам в твоём портфолио.

Подробнее о курсе

Группировка и обратные ссылки

Скобки () в regexp выполняют не только функцию группировки, но и запоминают совпадение. Группировка и обратные ссылки позволяют сохранить найденный фрагмент для дальнейшего использования, либо для ссылки на него внутри того же шаблона.

Существует два типа групп:

  1. Захватывающие (capturing): (pattern) — результат помещается в отдельный элемент массива.
  2. Незахватывающие (non-capturing): (?:pattern) — группа используется только для применения квантификатора, но не запоминается.

Обратная ссылка \1 ссылается на текст, совпавший с первой группой.

Пример 4

Поиск повторяющихся слов или проверка парных тегов.

let htmlTagExample = "<div>Текст внутри</div>";
let findDoubleTag = /<([a-z]+)>.*<\/\1>/; // \1 означает то же имя, что и в первой группе

console.log(findDoubleTag.test(htmlTagExample)); // true
console.log(htmlTagExample.match(findDoubleTag)[1]); // "div"

Жадный и ленивый поиск

По умолчанию квантификаторы работают в жадном режиме. Жадный и ленивый поиск — это важная концепция, которая отвечает на вопрос: "сколько текста захватить?".
  1. Жадный режим: квантификатор старается захватить максимально длинную строку, подходящую под условие.
  2. Ленивый режим: добавляем ? после квантификатора, и движок останавливается на первом же совпадении.

Пример 5

Парсим текст и вытаскиваем значения в кавычках.

let textWithQuotes = "Метка: \"красный\" и \"большой\".";
let greedyPattern = /".*"/g; // Найдет "красный" и "большой" как одну строку
let lazyPattern = /".*?"/g;  // Найдет "красный" и "большой" отдельно

console.log(textWithQuotes.match(greedyPattern)); // ["\"красный\" и \"большой\""]
console.log(textWithQuotes.match(lazyPattern));   // ["\"красный\"", "\"большой\""]

Флаги регулярных выражений

Флаги меняют глобальное поведение поиска. Они пишутся после закрывающего слеша или передаются вторым аргументом в конструктор.

Флаги регулярных выражений бывают следующих типов:

  1. g (global): продолжает поиск после первого совпадения, возвращая все результаты.
  2. i (ignore case): игнорирует регистр символов.
  3. m (multiline): меняет поведение ^ и $ так, что они работают не только с началом/концом всей строки, но и с началом/концом каждой строки внутри текста.
  4. s (dotAll): позволяет точке . находить символ перевода строки.
  5. u (unicode): включает полную поддержку юникода.
  6. y (sticky): поиск ведется строго с текущей позиции, заданной свойством lastIndex.

Пример 6

Работа с флагом m, который часто путают с флагом g.

let multiLineText = `Первый ряд
второй ряд
третий ряд`;

// Ищем строки, начинающиеся со слова "второй"
let startWithWord = /^второй/gm;
console.log(multiLineText.match(startWithWord)); // ["второй"]

Без флага m это выражение ничего бы не нашло, так как ^ смотрит только на начало всего текста «Первый ряд…», а не на начало второй строки.

Практика использования методов String и RegExp

Регулярные выражения бесполезны без методов, которые их применяют. В JavaScript есть как методы самого объекта RegExp, так и методы строк, которые принимают regexp. Практика использования методов String и RegExp показывает, какой инструмент выбирать для конкретной задачи.

Методы строк (String)

str.search(regexp) : возвращает индекс первого совпадения или -1.

Пример 7

let userComment = "Ошибка: файл не найден. Код: 0xAF3";
let errorCodeIndex = userComment.search(/0x[A-F0-9]+/);
console.log(errorCodeIndex); // Индекс начала "0xAF3"

str.match(regexp) : возвращает массив совпадений. Без флага g вернет детальную информацию (группы, индекс).

Пример 8

let invoiceData = "Сумма: 1500р, Скидка: 200р";
let amounts = invoiceData.match(/\d+(?=р)/g); // Ищем цифры перед "р"
console.log(amounts); // ["1500", "200"]

str.replace(regexp, newSubstr) : заменяет совпадения. Очень мощный инструмент.

Пример 9

let secretMessage = "Мой номер: 123-45-67";
let hiddenNumber = secretMessage.replace(/\d/g, "*");
console.log(hiddenNumber); // "Мой номер: ***-**-**"

str.split(regexp) : разбивает строку по разделителю.

Пример 10

let csvLine = "яблоко;груша;слива;апельсин";
let fruits = csvLine.split(/;\s*/); // Разделитель: точка с запятой и возможный пробел
console.log(fruits); // ["яблоко", "груша", "слива", "апельсин"]

Методы RegExp

regexp.test(str) : самый быстрый метод. Проверяет, есть ли совпадение, возвращает true/false. Идеален для валидации.

Пример 11

let userEnteredIp = "192.168.0.1";
let ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipPattern.test(userEnteredIp)) {
    console.log("Формат IP корректен");
}

regexp.exec(str) : продвинутый метод. При использовании с флагом g позволяет организовать итеративный поиск, запоминая позицию lastIndex.

Пограничные ситуации и якоря

Якоря ^ и $ проверяют позицию, а не символы. Пограничные ситуации и якоря помогают убедиться, что мы нашли именно целое слово или строку, а не его часть.

Кроме начала и конца строки, существует понятие границы слова \b. Это не символ, а место между символом из \w и не из \w (или началом/концом строки).

Пример 11

Находим слово «кот», но не трогаем «скунс» или «коттедж».

let zooStory = "У нас есть кот и скунс. Коттедж пустует.";
let findExactCat = /\bкот\b/gi; // \b гарантирует, что это отдельное слово

console.log(zooStory.replace(findExactCat, "пёс"));
// Результат: "У нас есть пёс и скунс. Коттедж пустует."

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

Это так называемые lookahead и lookbehind. Они проверяют, есть ли символы справа или слева от текущей позиции, но не включают их в результат. Опережающие и ретроспективные проверки позволяют создавать сложные условия, не захватывая лишнего.
  • x(?=y) — позитивный опережающий поиск: найти x, только если за ним следует y.
  • x(?!y) — негативный опережающий поиск: найти x, только если за ним НЕ следует y.
  • (?<=y)x — позитивный ретроспективный поиск: найти x, только если перед ним y.
  • (?<!y)x — негативный ретроспективный поиск: найти x, только если перед ним НЕ y.

Пример 12

Выделяем валюту без захвата символа валюты.

let priceList = "Цена: 25€, старая цена: 30$";
let findEuroPrices = /\d+(?=€)/g; // Ищем цифры, перед которыми стоит €
let findDollarPrices = /(?<=\$)\d+/g; // Ищем цифры, после которых идет $ (ретроспектива)

console.log(priceList.match(findEuroPrices)); // ["25"]
console.log(priceList.match(findDollarPrices)); // ["30"]

Типичные примеры для веб-разработки

В повседневной работе фронтенд-разработчика часто встречаются одни и те же паттерны. Типичные примеры для веб-разработки стоит держать под рукой.

Валидация email

Не пытайтесь написать идеальный regexp для email (это почти невозможно).

Пример 13

Базовая проверка на наличие «@» и домена.

let checkEmailSimple = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
console.log(checkEmailSimple.test("support@example.co.uk")); // true (домен .co.uk пройдет, так как есть точка)

Валидация номера телефона (РФ, простой формат)

Пример 14

Ждем ввод в формате +7(XXX)XXX-XX-XX.

let phoneInput = "+7(123)456-78-90";
let phonePattern = /^\+7\(\d{3}\)\d{3}-\d{2}-\d{2}$/;
console.log(phonePattern.test(phoneInput)); // true

Извлечение параметров из URL

Пример 15

Использование URLSearchParams (встроенный API браузера)

function obtainQueryValue(requiredField, urlToParse) {
    let urlObject = new URL(urlToParse);
    let searchParams = new URLSearchParams(urlObject.search);
    return searchParams.get(requiredField);
}

let sampleLink = "https://store.online/catalog?search=iphone&sort=price_asc&page=3&inStock=1";
console.log("Поисковый запрос: " + obtainQueryValue("search", sampleLink));
console.log("Сортировка: " + obtainQueryValue("sort", sampleLink));
console.log("Страница: " + obtainQueryValue("page", sampleLink));

Отладка и производительность

Регулярные выражения могут быть коварны: одно неправильно составленное выражение способно "подвесить" браузер. Отладка и производительность — важный этап написания regexp.

Главная проблема — «катастрофический возврат». Он возникает, когда вложенные квантификаторы ((a+)+) заставляют движок перебирать миллионы комбинаций, чтобы понять, что строка не подходит.

Советы по оптимизации

  • Избегайте излишней вложенности квантификаторов.
  • Конкретизируйте классы: \d работает быстрее, чем [0-9] (хотя разница мала), но [0-9] читабельнее.
  • Используйте атомарные группы и lookahead для обрыва ненужных проверок в сложных случаях.
  • Если нужно просто проверить наличие символа (true/false), используйте regexp.test(), а не str.match(), так как test работает быстрее.

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

Почему мое регулярное выражение работает не так, как я ожидаю, хотя в онлайн-тестере всё верно?

Скорее всего, вы забыли экранировать слеши или используете строку, переданную в конструктор new RegExp(). В литеральной записи /pattern/ слеши экранировать не нужно, а в строке для конструктора обратный слеш тоже нужно экранировать (например, "\\d" вместо "\d"). Кроме того, онлайн-тестеры часто автоматически добавляют флаг g или игнорируют lastIndex, который в реальном коде может сохраняться между вызовами метода exec().

Как найти текст, но исключить теги div или span из результата?

Используйте опережающие проверки. Например, чтобы найти текст между открывающим и закрывающим тегом <div>, но исключить сами теги, можно написать (?<=<div>).*?(?=<\/div>). Здесь (?<=…) проверяет наличие открывающего тега перед текстом, а (?=…) — наличие закрывающего после, но в результат попадает только то, что между ними. Не забывайте про ленивый квантификатор *?, чтобы захватить только ближайший закрывающий тег.

Чем отличается test() от exec() и когда что применять?

Метод test() возвращает только булево значение — есть совпадение или нет. Он самый быстрый и подходит для простых проверок вроде валидации формы. Метод exec() возвращает массив с деталями совпадения: саму строку, индекс, группы. Если вам нужно не просто проверить, а ещё и извлечь данные, используйте exec(). При этом помните, что с флагом g оба метода запоминают позицию в свойстве lastIndex.

Можно ли использовать регулярные выражения для парсинга HTML?

Технически — да, но мы настоятельно не рекомендуем этого делать. HTML — это контекстно-свободная грамматика, а регулярные выражения работают с регулярной грамматикой. Они не справляются с вложенными структурами, случайными комментариями или невалидной разметкой. Для простого извлечения атрибутов или текста из плоской структуры regexp подойдет, но для полноценного парсинга используйте DOMParser или библиотеки вроде cheerio.

Как проверить валидность числа с плавающей точкой без научной нотации?

Для этого подойдет выражение /^-?\d+(\.\d+)?$/. Оно проверяет: необязательный минус в начале, одну или более цифр, а затем опционально точку с одной или более цифрами после нее. Это не пропустит числа вида 5e-10 (экспоненциальная запись) и не допустит точку без цифр после нее. Если нужна поддержка чисел, начинающихся с точки (например, .5), модифицируйте шаблон до /^-?(?:\d+(?:\.\d*)?|\.\d+)$/.

Почему флаг g ломает работу с одним и тем же выражением?

Это связано со свойством lastIndex. Когда вы используете флаг g и вызываете метод exec() или test() несколько раз, поиск начинается не с начала строки, а с позиции, на которой остановился предыдущий вызов. Если вы хотите сбросить это поведение, либо обнуляйте lastIndex вручную (regexp.lastIndex = 0), либо создавайте новый объект регулярного выражения перед каждым новым поиском.

Заключение

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

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

Теги: