Регулярные выражения в JavaScript: синтаксис, примеры и лучшие практики
О чём статья
Я объясню, что такое регулярные выражения, как их писать и тестировать в JavaScript, приведу практические примеры и чек-листы для разработчика и тестировщика. Также разберём ситуации, когда regex не подходит, и альтернативы.
Кому полезно: начинающим разработчикам и инженерам качества, которые работают с валидацией, извлечением и трансформацией строк.
Краткое определение
Регулярное выражение — строка, описывающая шаблон поиска в тексте. Простое правило: чем конкретнее шаблон, тем быстрее и надёжнее он работает.
Основной синтаксис регулярных выражений в JavaScript
Есть два способа создать регулярное выражение:
- Литерал: шаблон между прямыми слешами. Например:
// Без флага
const regexExpression_1 = /pattern/;
// С флагом
const regexExpression_2 = /pattern/gi;- Конструктор RegExp:
const regexExpression = new RegExp("Pattern", "g");Флаги — необязательные параметры, которые изменяют поведение шаблона. Частые флаги:
- g — глобальный поиск (все совпадения);
- i — нечувствительность к регистру;
- m — многострочный режим (якоря ^ и $ работают для начала/конца строки);
- s — режим «dotAll»: точка (.) также совпадает с символом новой строки (ES2018+);
- u — режим Unicode; нужен при работе с суррогатными парами и кодовыми точками;
- y — «sticky» — поиск с привязкой к текущей позиции (редко используемый).
Применение нескольких флагов:
const regexExpression = new RegExp("Pattern", "gi");Этот пример найдёт все вхождения “Pattern” независимо от регистра.
Метасимволы и классы символов
Ниже — список часто используемых метасимволов и короткие пояснения с примерами.
- . — любой одиночный символ, кроме перевода строки (в режиме s точка совпадает и с переводом строки).
- — предыдущий элемент 0 и более раз (жадный квантификатор).
- — предыдущий элемент 1 и более раз.
- ? — предыдущий элемент 0 или 1 раз; также делает квантификатор ленивым при добавлении ? после него.
- {n} {n,} {n,m} — точное/минимальное/диапазонное количество повторов.
- ^ — начало строки/строки (в режиме m — начала каждой строки).
- $ — конец строки/строки.
- [] — класс символов; например, [aeiou] совпадёт с любой гласной.
- [^…] — отрицательный класс; [^0-9] — любой символ, не являющийся цифрой.
- | — альтернатива (логическое ИЛИ). Пример: cat|dog.
- () — захватывающая группа; позволяет извлекать подстроки и применять квантификаторы к группе.
- (?:…) — негруппирующая (non-capturing) группа; используется для группировки без сохранения в результат.
- \d, \D, \w, \W, \s, \S — сокращения: цифры/не-цифры, «слово»/не-слово, пробел/не-пробел.
- \b, \B — граница слова / не граница слова.
- (?=…), (?!…) — позитивный/негативный просмотр вперёд (lookahead).
- (?<=…), (?
Примеры:
// Любая почта простого вида
const email = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/i;
// Номер телефона (US-подобный)
const phone = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/;Важно: квадратные скобки [] и экранирование символов работают по своим правилам: внутри [] многие метасимволы теряют особый смысл, но дефис (-) и ^ имеют специальное поведение.
Группы и захват
Группы позволяют взять часть совпадения и получить её отдельно в результате. Порядок групп определяется скобками слева направо.
let regex = /(\d{4})-(\d{2})-(\d{2})/; // Группа 1: год, 2: месяц, 3: день
let date = "2024-01-07";
let result = regex.exec(date);
// result[1] -> "2024"
// result[2] -> "01"
// result[3] -> "07"Если группа нужна только для группировки (без захвата), используйте (?:…):
const r = /(?:foo|bar)baz/; // Группа не попадёт в массив захватаМетоды проверки и получения совпадений
В JavaScript есть несколько полезных методов для работы с регулярными выражениями.
test
.test(string) возвращает boolean — есть ли совпадение.
let regex = /.com$/;
let str = "example.com";
console.log(regex.test(str)); // trueexec
.exec(string) возвращает массив с совпадением и захваченными группами или null.
let regex = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/;
let str = "123-456-7890";
let result = regex.exec(str);
if (result !== null) {
console.log(`${result[0]} is a valid phone number`);
} else {
console.log("Invalid phone number");
}Если регулярное выражение с флагом g используется многократно с exec, оно запоминает позицию поиска (lastIndex).
match и matchAll
- str.match(regex) возвращает массив совпадений или массив с группами (в зависимости от флагов).
- str.matchAll(regex) возвращает итератор для всех совпадений с доступом к группам (полезно для получения всех групп при глобальном поиске).
const text = "a1 b2 c3";
const re = /(\w)(\d)/g;
for (const m of text.matchAll(re)) {
console.log(m[1], m[2]);
}replace
str.replace(regex, replacement) заменяет совпадения на replacement. Replacement может быть строкой или функцией.
let string = "The quick brown fox jumps over the lazy dog.";
let expression = /The/gi;
let newString = string.replace(expression, "a");
console.log(newString); // "a quick brown fox jumps over a lazy dog."
// Replacement как функция
let names = "Mr. Smith and Ms. Doe".replace(/(Mr|Ms)\.\s(\w+)/g, (m, title, name) => `${name} (${title})`);
// "Smith (Mr) and Doe (Ms)"search и split
- str.search(regex) возвращает индекс первого совпадения или -1.
- str.split(regex) разбивает строку по совпадениям регулярного выражения.
Жадные и ленивые квантификаторы
По умолчанию квантификаторы жадные — они захватывают как можно больше символов. Добавление ? делает их ленивыми.
const s = "onetwo";
// Жадный
console.log(s.match(/.*<\/div>/)[0]); // совпадёт с обеими дивами
// Ленивый
console.log(s.match(/.*?<\/div>/g)); // два отдельных совпаденияКогда регулярные выражения не подходят
- Сложная вложенная грамматика (HTML, XML): лучше использовать парсеры.
- Контекстно-зависимый синтаксис (языки программирования): парсер AST надёжнее.
- Чувствительность к Unicode-границам без флага u: могут быть ошибки.
Альтернатива: синтаксический разбор на основе грамматик, парсеры, специализированные библиотеки для обработки HTML/JSON.
Типичные ошибки и крайние случаи
- Неправильное экранирование слешей при использовании конструктора RegExp: new RegExp(“\d+”) вместо new RegExp(“\d+”).
- Использование глобального флага g с методами, которые возвращают только первый результат (некоторые комбинации приводят к неожиданному lastIndex).
- Катастрофическое обратное отслеживание (catastrophic backtracking) при вложенных жадных квантификаторах.
Пример плохого паттерна:
// Плохо: (a+)+ приведёт к экспоненциальному времени при длинных строках без совпадения
const bad = /(a+)+b/;
Митигирование: сделать паттерны более конкретными, использовать ленивые квантификаторы, избегать вложения неопределённых квантификаторов.
Производительность и безопасность
- Чем уже и конкретнее шаблон, тем меньше вероятность затрат на backtracking.
- Проверяйте входные данные и по возможности ограничивайте длину строки до проверки regex.
- Для пользовательских шаблонов выполняйте анализ на предмет уязвимости ReDoS (Regular Expression Denial of Service).
Совет: вместо разрешения пользователям вводить произвольные шаблоны предоставьте ограниченный DSL или готовые шаблоны.
Практическая методология создания надёжного регулярного выражения
- Чётко опишите цель: что именно нужно найти или извлечь.
- Начните с простого, затем постепенно усложняйте шаблон.
- Покройте позитивные и негативные тест-кейсы.
- Проанализируйте пограничные случаи (пустые строки, неожиданные символы).
- Проверяйте производительность на реальных данных.
Чек-листы по ролям
Разработчик:
- Описана цель шаблона.
- Используются флаги корректно (g, i, m, s, u).
- Экранированы специальные символы.
- Добавлены unit-тесты для позитивных и негативных кейсов.
QA-инженер:
- Проверить вхождения на реальных данных.
- Тесты на очень большие строки и невалидные данные.
- Проверить поведение при режиме многострочности и флаге s.
Инженер безопасности:
- Поиск потенциального ReDoS-паттерна.
- Ограничение длины входа перед запуском regex.
- Логи и метрики выполнения регулярных выражений.
Критерии приёмки
- Регулярное выражение покрывает все заявленные позитивные тесты.
- Результаты отрицательных тестов не содержат ложных срабатываний.
- Производительность соответствует требованиям (не происходит экспоненциального роста времени).
- Код снабжён комментариями и примерами использования.
Тестовые примеры и критерии
- Валидация email (базовый):
- “user@example.com” -> совпадает
- “user@sub.domain.org” -> совпадает
- “user@@example.com” -> не совпадает
- Формат телефона (US):
- “(123) 456-7890” -> совпадает
- “123-456-7890” -> совпадает
- “12-3456-7890” -> не совпадает
- Извлечение тегов HTML (только демонстрация):
- На HTML-парсинг использовать парсер, а не regex — тест: “
text
” -> не полагаться на regex для вложенных структур.
Маленькое справочное поле — ключевые факты
- Конструктор RegExp требует дополнительного экранирования, потому что строка сначала интерпретируется как литерал JavaScript.
- Lookbehind (?<=…) официально поддерживается в современных движках JavaScript (ES2018+).
- Флаг s (dotAll) позволяет точке совпадать с переносом строки (ES2018+).
Короткий глоссарий
- Захват (capture): сохранение части совпадения в массив результатов.
- Квантификатор: оператор, задающий количество повторов (например, *, +, {2,5}).
- Backtracking: механизм поиска альтернатив при несовпадении, может привести к ухудшению производительности.
Примеры полезных шаблонов — шпаргалка
// Простая проверка URL (очень базовая)
const url = /https?:\/\/[\w.-]+(?:\.[\w\.-]+)+[\w\-._~:\/?#\[\]@!$&'()*+,;=.]+/i;
// Валидатор IPv4
const ipv4 = /^(25[0-5]|2[0-4]\d|[01]?\d?\d)(\.(25[0-5]|2[0-4]\d|[01]?\d?\d)){3}$/;
// Получить все слова, игнорируя пунктуацию
const words = /\b[\p{L}0-9_-]+\b/gu; // требует флага u для Unicode (ES2018+)
Альтернативные подходы
- Для структурированных форматов (JSON, XML, HTML) используйте специализированные парсеры.
- Для простых валидаций достаточно комбинации методов String (startsWith, includes, split) и небольших проверок.
Быстрые рекомендации по локализации и работе с Unicode
- Всегда используйте флаг u при работе с Unicode-данными и символами за пределами Basic Multilingual Plane.
- Для поиска слов используйте \p{L} (требует флага u) — это лучший способ охватить буквы разных алфавитов.
Пример реального рабочего сценария: валидация поля ввода на форме
- Обрежьте пробелы: value = value.trim();
- Проверьте длину: если длиннее N — отклонить.
- Примените чистый и определённый regex (не общую «ловушку»).
- При неудаче — верните понятное сообщение пользователю.
Часто задаваемые вопросы
Что лучше: RegExp-конструктор или литерал?
Литерал проще и быстрее, если шаблон известен на этапе написания кода. Конструктор полезен, когда шаблон формируется динамически.
Как отладить сложный шаблон?
Используйте онлайн-отладчики (Regex101, Regexper) и добавляйте unit-тесты с разнообразными кейсами. Для локальных данных логируйте время работы.
Чем опасны пользовательские regex?
Пользовательский сложный паттерн может создать ReDoS-уязвимость. Ограничьте длину входа и предоставляйте безопасные шаблоны.
Резюме
- Регулярные выражения — мощный инструмент для поиска и трансформации текста, но требуют аккуратности.
- Предпочитайте конкретные шаблоны, проверяйте граничные случаи и избегайте вложенных жадных квантификаторов.
- Для структурированных данных выбирайте парсеры.
Важно: всегда сопровождайте сложные регулярные выражения комментариями и тестами.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone