التعبيرات المنتظمة في TypeScript: دليل شامل
Что это такое и зачем? (аналогия из реальной жизни)
Представь, что ты — библиотекарь в огромной библиотеке, где хранятся миллионы книг. К тебе приходит читатель и говорит: «Мне нужны все книги, в названии которых есть слово "приключения", но только те, что написаны после 2000 года, и чтобы на обложке была картинка с драконом». Если бы ты просматривал каждую книгу вручную, это заняло бы недели. Но у тебя есть волшебная лупа, которая умеет мгновенно находить книги по описанию. Ты просто описываешь лупе, что искать, и она за секунду показывает все подходящие книги.
Регулярные выражения (Regular Expressions, или сокращённо regexp, regex) — это и есть такая «волшебная лупа» для текста. Это специальный язык, который позволяет описывать шаблоны (паттерны) поиска в строках. С помощью регулярных выражений ты можешь:
- Проверить, соответствует ли строка определённому формату (например, является ли строка email-адресом или номером телефона);
- Найти в тексте все слова, которые подходят под описание (например, все слова из 5 букв, начинающиеся на «а»);
- Заменить часть текста по шаблону (например, заменить все даты из формата «дд.мм.гггг» на «гггг-мм-дд»);
- Разбить строку на части по определённому правилу.
💡 Важно понимать: Регулярные выражения — это не функция и не метод конкретного языка программирования. Это самостоятельный язык, который встроен в TypeScript (и JavaScript) и во многие другие языки. Изучив его один раз, ты сможешь использовать его везде!
В реальной жизни регулярные выражения используются повсеместно: от проверки форм на сайтах (правильно ли введён пароль?) до поиска ошибок в коде и обработки огромных массивов данных. Без них работа программиста была бы в разы сложнее и медленнее.
Теория: как это работает изнутри
Чтобы понять, как работают регулярные выражения, нужно разобраться с их «кирпичиками». Любое регулярное выражение — это последовательность символов, которая описывает шаблон. Движок регулярных выражений (встроенная программа в TypeScript) берёт этот шаблон и «проходит» по строке, пытаясь найти совпадение.
Основные понятия
1. Литералы (обычные символы)
Самый простой случай: если ты напишешь в шаблоне обычный текст, движок будет искать этот текст буквально. Например, шаблон cat найдёт в строке «I have a cat» слово «cat». Но он не найдёт «Cat» (с большой буквы), потому что регистр имеет значение.
2. Метасимволы (специальные символы)
Это символы, которые имеют особое значение. Вот самые важные из них:
| Символ | Значение | Пример |
|---|---|---|
. |
Любой одиночный символ (кроме перевода строки) | c.t найдёт «cat», «cot», «c4t» |
d |
Любая цифра (0-9) | dd найдёт «42», «00», «99» |
w |
Любая буква, цифра или знак подчёркивания | ww найдёт «a1», «_b», «X9» |
s |
Любой пробельный символ (пробел, табуляция, перевод строки) | wordsword найдёт «word word» |
^ |
Начало строки | ^Hello найдёт «Hello» только в начале строки |
$ |
Конец строки | world$ найдёт «world» только в конце строки |
|
Граница слова | cat найдёт «cat», но не «catalog» |
3. Квантификаторы (указатели количества)
Они говорят, сколько раз должен повториться предыдущий символ или группа:
| Квантификатор | Значение | Пример |
|---|---|---|
* |
0 или более раз | go* найдёт «g», «go», «goo», «gooo» |
+ |
1 или более раз | go+ найдёт «go», «goo», но не «g» |
? |
0 или 1 раз (необязательный символ) | colou?r найдёт «color» и «colour» |
{n} |
Ровно n раз | d{3} найдёт ровно 3 цифры подряд |
{n,} |
n или более раз | d{2,} найдёт 2 и более цифр |
{n,m} |
От n до m раз | d{2,4} найдёт от 2 до 4 цифр |
4. Классы символов (группы)
Иногда нужно указать не один символ, а целый набор возможных вариантов. Для этого используются квадратные скобки:
[abc]— любой из символов a, b или c;[a-z]— любая строчная буква латинского алфавита;[0-9]— любая цифра (то же самое, чтоd);[^abc]— любой символ, кроме a, b или c (знак ^ внутри скобок означает отрицание).
5. Группы и альтернативы
Круглые скобки () используются для группировки частей шаблона. Это нужно, чтобы применить квантификатор к целой группе или чтобы «запомнить» найденную часть для дальнейшего использования. Вертикальная черта | означает «или»:
(cat|dog)найдёт либо «cat», либо «dog»;(ha){3}найдёт «hahaha» (три повторения группы «ha»).
⚠️ Важное замечание: В TypeScript (и JavaScript) регулярные выражения создаются двумя способами: с помощью литерала/pattern/flagsили конструктораnew RegExp('pattern', 'flags'). Литерал удобнее для статических шаблонов, конструктор — когда шаблон создаётся динамически.
Синтаксис в TypeScript: все формы и варианты
В TypeScript для работы с регулярными выражениями используется встроенный объект RegExp. Давай рассмотрим все способы его создания и основные методы.
Создание регулярного выражения
// Способ 1: Литерал (самый распространённый)
// Шаблон заключается в слеши, после них могут идти флаги
const regexLiteral: RegExp = /hello/gi;
// Способ 2: Конструктор (полезен, когда шаблон динамический)
const pattern: string = "hello";
const flags: string = "gi";
const regexConstructor: RegExp = new RegExp(pattern, flags);
// Оба способа создают объект одного и того же типа RegExp
console.log(typeof regexLiteral); // "object"
console.log(regexLiteral instanceof RegExp); // true
Флаги (модификаторы поведения)
Флаги — это буквы, которые идут после закрывающего слеша (или вторым аргументом конструктора) и меняют поведение поиска:
| Флаг | Название | Описание |
|---|---|---|
g |
global | Ищет все совпадения, а не только первое |
i |
ignore case | Игнорирует регистр букв |
m |
multiline | Меняет поведение ^ и $ — они начинают работать с каждой строкой внутри текста |
s |
dotAll | Позволяет точке . находить также символ перевода строки |
u |
unicode | Включает полную поддержку Unicode (нужен для работы с эмодзи и спецсимволами) |
y |
sticky | Ищет совпадение только начиная с текущей позиции в строке |
Основные методы RegExp и String
Регулярные выражения в TypeScript используются совместно с методами строк и самого объекта RegExp. Вот самые важные из них:
| Метод | Где определён | Что делает | Возвращает |
|---|---|---|---|
test() |
RegExp | Проверяет, есть ли совпадение в строке | boolean |
exec() |
RegExp | Ищет совпадение и возвращает подробную информацию | RegExpExecArray | null |
match() |
String | Ищет совпадения в строке | string[] | null |
matchAll() |
String | Ищет все совпадения (с флагом g) и возвращает итератор | IterableIterator<RegExpExecArray> |
replace() |
String | Заменяет совпадения на указанную строку | string |
replaceAll() |
String | Заменяет все совпадения (не требует флага g) | string |
search() |
String | Ищет первое совпадение и возвращает его индекс | number (-1 если не найдено) |
split() |
String | Разбивает строку по совпадениям с шаблоном | string[] |
Специальные символы в строках (экранирование)
Если тебе нужно найти символ, который является метасимволом (например, точку или звёздочку), его нужно «экранировать» — поставить перед ним обратную косую черту :
// Ищем буквально точку в конце предложения
const regexDot: RegExp = /.$/;
console.log(regexDot.test("Привет.")); // true
console.log(regexDot.test("Привет")); // false
// Ищем звёздочку в тексте
const regexStar: RegExp = /*/;
console.log(regexStar.test("2 * 3 = 6")); // true
💡 Совет: В TypeScript, если ты используешь конструкторnew RegExp(), помни, что обратную косую черту нужно дублировать, потому что строки тоже используют её для экранирования. Например:new RegExp('\.')будет искать точку.
Примеры кода — от простого к сложному
Теперь давай посмотрим на реальные примеры. Каждый пример — полностью рабочий код с подробными комментариями.
Пример 1: Проверка, содержит ли строка цифры
// Задача: проверить, есть ли в строке хотя бы одна цифра
const text1: string = "Привет, мне 25 лет!";
const text2: string = "Привет, мир!";
// Создаём регулярное выражение: d — любая цифра
const hasDigitRegex: RegExp = /d/;
// Метод test() возвращает true или false
console.log(`"${text1}" содержит цифры?`, hasDigitRegex.test(text1)); // true
console.log(`"${text2}" содержит цифры?`, hasDigitRegex.test(text2)); // false
// Объяснение: d — это класс символов, который соответствует любой цифре от 0 до 9.
// Метод test() просто проверяет, есть ли хотя бы одно совпадение в строке.
Пример 2: Поиск всех email-адресов в тексте
// Задача: найти все email-адреса в тексте
const emailText: string = "Свяжитесь с нами: support@example.com или info@test.org. Также пишите на admin@site.net.";
// Шаблон для email (упрощённый):
// [w.-]+ — один или более символов: буквы, цифры, _, ., -
// @ — символ собаки
// [w.-]+ — домен
// . — точка (экранированная)
// [a-zA-Z]{2,} — доменная зона (от 2 букв)
const emailRegex: RegExp = /[w.-]+@[w.-]+.w{2,}/g;
// Метод match() возвращает массив всех совпадений (благодаря флагу g)
const foundEmails: string[] | null = emailText.match(emailRegex);
if (foundEmails) {
console.log("Найденные email-адреса:", foundEmails);
// Вывод: ["support@example.com", "info@test.org", "admin@site.net"]
} else {
console.log("Email-адреса не найдены");
}
// Объяснение: Флаг g (global) заставляет искать все совпадения, а не только первое.
// Класс [w.-] включает буквы, цифры, подчёркивание, точку и дефис.
// .{2,} означает "от 2 символов" для доменной зоны (com, org, net и т.д.)
Пример 3: Проверка номера телефона в формате +7 (XXX) XXX-XX-XX
// Задача: проверить, соответствует ли строка формату российского номера телефона
const phone1: string = "+7 (123) 456-78-90";
const phone2: string = "8 (123) 456-78-90";
const phone3: string = "+7 123 456 78 90";
// Шаблон:
// ^ — начало строки
// +7 — символ + и цифра 7 (плюс экранируем, так как + — метасимвол)
// s — пробел
// ( — открывающая скобка (экранируем)
// d{3} — ровно 3 цифры
// ) — закрывающая скобка
// s — пробел
// d{3} — 3 цифры
// - — дефис
// d{2} — 2 цифры
// - — дефис
// d{2} — 2 цифры
// $ — конец строки
const phoneRegex: RegExp = /^+7s(d{3})sd{3}-d{2}-d{2}$/;
console.log(`"${phone1}" — корректный номер?`, phoneRegex.test(phone1)); // true
console.log(`"${phone2}" — корректный номер?`, phoneRegex.test(phone2)); // false (нет +7)
console.log(`"${phone3}" — корректный номер?`, phoneRegex.test(phone3)); // false (нет скобок и дефисов)
// Объяснение: ^ и $ — якоря, которые требуют, чтобы строка полностью соответствовала шаблону.
// Без них шаблон искал бы совпадение в любой части строки.
// Экранирование ( и ) нужно, чтобы искать буквальные скобки, а не группу.
Пример 4: Замена дат из формата ДД.ММ.ГГГГ на ГГГГ-ММ-ДД
// Задача: преобразовать даты в международный формат
const dateText: string = "Сегодня 15.03.2024, а завтра будет 16.03.2024.";
// Шаблон для поиска даты:
// (d{2}) — группа 1: две цифры (день)
// . — точка
// (d{2}) — группа 2: две цифры (месяц)
// . — точка
// (d{4}) — группа 3: четыре цифры (год)
const dateRegex: RegExp = /(d{2}).(d{2}).(d{4})/g;
// В строке замены $1, $2, $3 ссылаются на захваченные группы
// Меняем порядок: год-месяц-день
const convertedText: string = dateText.replace(dateRegex, "$3-$2-$1");
console.log("Исходный текст:", dateText);
console.log("После замены:", convertedText);
// Вывод: "Сегодня 2024-03-15, а завтра будет 2024-03-16."
// Объяснение: Круглые скобки создают "захватывающие группы".
// В строке замены $1 означает "первая захваченная группа", $2 — вторая и т.д.
// Это мощный инструмент для перестановки частей текста.
Пример 5: Извлечение всех URL из HTML-текста
// Задача: найти все ссылки в HTML-коде
const htmlText: string = `
Сайт 1
Сайт 2
Сайт 3
`;
// Шаблон для поиска href:
// href= — буквально
// ["'] — открывающая кавычка (одинарная или двойная)
// ([^"']+) — группа 1: один или более символов, не являющихся кавычкой
// ["'] — закрывающая кавычка
const urlRegex: RegExp = /href=["']([^"']+)["']/g;
// Используем matchAll() для получения подробной информации о каждом совпадении
const matches = htmlText.matchAll(urlRegex);
console.log("Найденные URL:");
for (const match of matches) {
// match[0] — полное совпадение (например, href="https://example.com")
// match[1] — первая захваченная группа (сам URL)
console.log(` - ${match[1]}`);
}
// Вывод:
// - https://example.com
// - http://test.org/page
// - https://site.net?query=value
// Объяснение: [^"']+ означает "один или более символов, которые НЕ являются кавычкой".
// Это гарантирует, что мы захватим всё содержимое между кавычками.
// matchAll() возвращает итератор, который можно обойти в цикле for...of.
Пример 6: Проверка сложности пароля
// Задача: проверить, что пароль соответствует требованиям:
// - минимум 8 символов
// - хотя бы одна заглавная буква
// - хотя бы одна строчная буква
// - хотя бы одна цифра
// - хотя бы один специальный символ
function validatePassword(password: string): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
// Проверка длины: ^.{8,}$ — строка из 8 и более любых символов
if (!/^.{8,}$/.test(password)) {
errors.push("Пароль должен содержать минимум 8 символов");
}
// Проверка заглавной буквы: [A-Z] — любая заглавная латинская буква
if (!/[A-Z]/.test(password)) {
errors.push("Пароль должен содержать хотя бы одну заглавную букву");
}
// Проверка строчной буквы: [a-z] — любая строчная латинская буква
if (!/[a-z]/.test(password)) {
errors.push("Пароль должен содержать хотя бы одну строчную букву");
}
// Проверка цифры: d — любая цифра
if (!/d/.test(password)) {
errors.push("Пароль должен содержать хотя бы одну цифру");
}
// Проверка спецсимвола: [^A-Za-z0-9] — любой символ, не являющийся буквой или цифрой
if (!/[^A-Za-z0-9]/.test(password)) {
errors.push("Пароль должен содержать хотя бы один специальный символ");
}
return {
isValid: errors.length === 0,
errors: errors
};
}
// Тестируем
const testPasswords: string[] = ["weak", "Strong1", "Strong1!"];
testPasswords.forEach(pwd => {
const result = validatePassword(pwd);
console.log(`Пароль "${pwd}": ${result.isValid ? "✓ Корректен" : "✗ Ошибки: " + result.errors.join(", ")}`);
});
// Вывод:
// Пароль "weak": ✗ Ошибки: Пароль должен содержать минимум 8 символов, ...
// Пароль "Strong1": ✗ Ошибки: Пароль должен содержать хотя бы один специальный символ
// Пароль "Strong1!": ✓ Корректен
// Объяснение: Вместо одного сложного регулярного выражения мы используем несколько простых.
// Это делает код понятнее и позволяет давать конкретные сообщения об ошибках.
Пример 7: Разбор логов сервера
// Задача: извлечь информацию из строки лога Apache/Nginx
const logLine: string = '192.168.1.1 - - [10/Mar/2024:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 2326';
// Шаблон для разбора лога:
// (S+) — группа 1: IP-адрес (один или более непробельных символов)
// s+ — один или более пробелов
// [ — открывающая скобка (экранированная)
// ([^]]+) — группа 2: дата (один или более символов, не являющихся ])
// ] — закрывающая скобка
// s+ — пробелы
// " — кавычка
// ([^"]+) — группа 3: HTTP-запрос (один или более символов, не являющихся ")
// " — кавычка
// s+ — пробелы
// (d+) — группа 4: статус-код (одна или более цифр)
// s+ — пробелы
// (d+) — группа 5: размер ответа (одна или более цифр)
const logRegex: RegExp = /(S+)s+S+s+S+s+[([^]]+)]s+"([^"]+)"s+(d+)s+(d+)/;
const logMatch = logLine.match(logRegex);
if (logMatch) {
console.log("Разбор лога:");
console.log(` IP-адрес: ${logMatch[1]}`); // 192.168.1.1
console.log(` Дата и время: ${logMatch[2]}`); // 10/Mar/2024:13:55:36 +0000
console.log(` HTTP-запрос: ${logMatch[3]}`); // GET /index.html HTTP/1.1
console.log(` Статус-код: ${logMatch[4]}`); // 200
console.log(` Размер ответа: ${logMatch[5]} байт`); // 2326
} else {
console.log("Не удалось разобрать лог");
}
// Объяснение: S+ означает "один или более непробельных символов".
// [^]]+ — это класс с отрицанием: "один или более символов, не являющихся ]".
// Этот пример показывает, как с помощью одного регулярного выражения можно разобрать сложную строку.
Сравнение с Python: чем похоже и чем отличается
Если ты уже знаком с Python или планируешь изучать его, полезно знать различия в работе с регулярными выражениями. В Python используется модуль re, а в TypeScript — встроенный объект RegExp.
| Характеристика | TypeScript (JavaScript) | Python |
|---|---|---|
| Создание | Литерал /pattern/flags или new RegExp() |
Только через re.compile() или функции модуля |
| Флаги | После закрывающего слеша: /pattern/gi |
Аргументом: re.findall(pattern, text, re.IGNORECASE) |
| Поиск всех совпадений | str.match(regex) с флагом g |
re.findall(pattern, text) (всегда ищет все) |
| Замена | str.replace(regex, replacement) |
re.sub(pattern, replacement, text) |
| Именованные группы | (?<name>pattern) |
(?P<name>pattern) (другой синтаксис) |
| Unicode | Флаг u для полной поддержки |
По умолчанию поддерживает Unicode (строки — Unicode) |
| Режим многострочности | Флаг m меняет поведение ^ и $ |
Флаг re.MULTILINE |
| Производительность | Хорошая, но может страдать при сложных шаблонах | В целом быстрее для сложных операций |
Главные отличия:
- Синтаксис создания: В TypeScript ты можешь использовать удобный литерал
/.../, в Python — только вызов функций. - Флаг global: В TypeScript без флага
gметодыmatch()иexec()возвращают только первое совпадение. В Pythonre.findall()всегда возвращает все. - Именованные группы: Синтаксис отличается: в TypeScript
(?<name>...), в Python(?P<name>...). - Работа с Unicode: В Python строки по умолчанию Unicode, в TypeScript нужно явно указывать флаг
uдля корректной работы с некоторыми символами.
💡 Совет: Если ты переходишь с Python на TypeScript (или наоборот), обращай внимание на флаги. Самая частая ошибка — забыть флаг g в TypeScript, когда нужно найти все совпад