Регулярное выражение в JavaScript — это объект, описывающий текстовый шаблон. Создаётся с помощью литерала `/pattern/flags` или `new RegExp('pattern', 'flags')`.
**Три самых используемых метода**
```javascript
const text = 'User: alice, age 30';
/\d+/.test(text) // true - есть ли паттерн где-нибудь в строке?
text.match(/\d+/) // ['30'] - первое совпадение как массив (или null)
text.replace(/\d+/, '??') // 'User: alice, age ??' - заменить первое совпадение
text.replace(/\d+/g, '??')// 'User: alice, age ??' - заменить ВСЕ (флаг g)
```
**Читаем паттерн посимвольно**
Возьмём `/([\w.]+)@([\w.]+\.\w{2,})/` (простой паттерн email):
```
( открыть группу 1
[\w.]+ один или более: символы слова или литеральная точка
) закрыть группу 1
@ литеральный "@"
( открыть группу 2
[\w.]+ один или более: символы слова или литеральная точка
\. литеральная точка (экранированная - обычная . означает "любой символ")
\w{2,} 2 или более символов слова (домен верхнего уровня)
) закрыть группу 2
```
```javascript
const m = '[email protected]'.match(/([\w.]+)@([\w.]+\.\w{2,})/);
m[0] // '[email protected]' - полное совпадение
m[1] // 'bob' - группа 1
m[2] // 'example.com' - группа 2
```
**Литерал regex vs new RegExp**
Предпочитай литерал `/pattern/` — он компилируется один раз и не требует экранирования строк. Используй `new RegExp(str)` только когда паттерн строится во время выполнения:
```javascript
// Хорошо: литерал
const valid = /^\d{3}-\d{4}$/;
// Когда паттерн из переменной:
function buildSearch(word) {
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return new RegExp(escaped, 'gi');
}
```
**Ловушка `lastIndex` с сохранением состояния**
Когда используешь флаг `g` или `y` и вызываешь `.test()` или `.exec()` на *том же* объекте regex несколько раз, движок запоминает позицию через `regex.lastIndex`. Это приводит к удивительному чередованию true/false:
```javascript
const re = /\d+/g; // с сохранением состояния из-за 'g'
re.test('abc 42 def'); // true (нашёл '42', lastIndex -> 6)
re.test('abc 42 def'); // false (продолжает с индекса 6, ничего не находит)
re.test('abc 42 def'); // true (lastIndex сбрасывается после неудачи)
// Безопасный паттерн: создавай новый regex каждый раз или сбрасывай вручную
re.lastIndex = 0;
re.test('abc 42 def'); // true (всегда)
```
Самое безопасное правило: используй флаг `g` только с `str.match()`, `str.matchAll()` или `str.replaceAll()`, а не с повторными вызовами `.test()` на том же объекте regex.
Захватывающие группы, именованные группы и matchAll
**Захватывающие группы** позволяют извлекать части совпадения. Результат `.match()` без флага `g` содержит полное совпадение по индексу 0, затем каждую группу.
```javascript
const log = '2024-03-15 ERROR disk full';
const m = log.match(/(\d{4}-\d{2}-\d{2}) (\w+) (.+)/);
m[1] // '2024-03-15'
m[2] // 'ERROR'
m[3] // 'disk full'
```
**Именованные группы** `(?<name>...)` — доступ через `m.groups.name`:
```javascript
const m = log.match(/(?<date>\d{4}-\d{2}-\d{2}) (?<level>\w+) (?<msg>.+)/);
m.groups.date // '2024-03-15'
m.groups.level // 'ERROR'
m.groups.msg // 'disk full'
```
Именованные группы также доступны в `replace` через `$<name>`:
```javascript
'2024-03-15'.replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, '$<d>/$<m>/$<y>')
// '15/03/2024'
```
**replace с функцией**
Когда замена является функцией, она получает `(fullMatch, ...groups, offset, originalStr)`:
```javascript
'hello world'.replace(/(\w+)/g, (match) => match.toUpperCase())
// 'HELLO WORLD'
'a=3, b=7'.replace(/(\w+)=(\d+)/g, (_, key, val) => `${key}=${+val * 2}`)
// 'a=6, b=14'
```
**matchAll — итерация по всем совпадениям с группами**
`.match()` с флагом `g` возвращает только полные совпадения (группы отбрасываются). Используй `str.matchAll(regex)`, чтобы получить все совпадения *вместе* с их группами:
```javascript
const text = 'alice=30, bob=25';
const re = /(\w+)=(\d+)/g;
for (const m of text.matchAll(re)) {
console.log(m[1], m[2]); // 'alice' '30' затем 'bob' '25'
}
// или собрать результаты:
const pairs = [...text.matchAll(re)].map(m => ({ name: m[1], age: +m[2] }));
// [{ name: 'alice', age: 30 }, { name: 'bob', age: 25 }]
```
`matchAll` требует флаг `g` и возвращает итератор — разворачивай или итерируй с `for...of`.
**Не-захватывающие группы** `(?:...)` группируют без индекса захвата — удобны для чередования:
```javascript
// Найти 'colour' или 'color'
'colour and color'.match(/colou?r/g) // ['colour', 'color']
// Чередование без захватывающей группы
'cats and dogs'.match(/(?:cat|dog)s?/g) // ['cats', 'dogs']
```
Lookahead, lookbehind, жадные/ленивые и динамический regex
**Lookahead** проверяет, что идёт *после* текущей позиции, не поглощая символов.
```javascript
// Позитивный lookahead (?=...) - совпадение X, только если после него Y
'100px 200em 300px'.match(/\d+(?=px)/g) // ['100', '300'] (числа перед 'px')
// Негативный lookahead (?!...) - совпадение X, только если после него НЕ Y
'100px 200em'.match(/\d+(?!px)/g) // ['200'] (числа не перед 'px')
```
**Lookbehind** (ES2018) проверяет, что идёт *перед*:
```javascript
// Позитивный lookbehind (?<=...) - совпадение X, только если перед ним Y
'$100 EUR200'.match(/(?<=\$)\d+/g) // ['100'] (числа после '$')
// Негативный lookbehind (?<!...) - совпадение X, только если перед ним НЕ Y
'$100 EUR200'.match(/(?<!\$)\d+/g) // ['200'] (числа не после '$')
```
**Жадные vs ленивые**
Квантификаторы по умолчанию жадные. Добавь `?`, чтобы сделать их ленивыми:
```javascript
const html = '<b>bold</b> and <i>italic</i>';
html.match(/<.+>/g) // ['<b>bold</b> and <i>italic</i>'] жадный
html.match(/<.+?>/g) // ['<b>', '</b>', '<i>', '</i>'] ленивый
```
**Экранирование ввода пользователя для динамического regex**
Если строишь regex из строки, введённой пользователем, сначала обязательно экранируй все специальные символы regex — иначе точка или звёздочка сломают паттерн:
```javascript
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function highlight(text, query) {
const re = new RegExp(escapeRegex(query), 'gi');
return text.replace(re, '<mark>$&</mark>');
}
highlight('Цена: 2.50', '2.50') // 'Цена: <mark>2.50</mark>'
// без экранирования '.' совпала бы с любым символом
```
**Типичные ловушки**
- Забыть флаг `g`: `'aaa'.replace(/a/, 'b')` даёт `'baa'`, а не `'bbb'`
- `.` не совпадает с переносами строк по умолчанию — используй `[\s\S]` или флаг `s`
- `^` и `$` совпадают с границами строки, а не строки текста — если не используется флаг `m`
- `.test()` в цикле с тем же `g` regex вызывает смещение `lastIndex` (см. Блок 1)
- Regex не может парсить HTML — используй DOM
Готовая коллекция паттернов для распространённых задач в JavaScript.
**Email (простой)**
```javascript
const EMAIL = /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g;
'[email protected], [email protected]'.match(EMAIL)
// ['[email protected]', '[email protected]']
```
**ISO дата**
```javascript
const ISO = /\b(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\b/g;
'Мероприятие 2024-03-15, дедлайн 2024-12-31'.match(ISO)
// ['2024-03-15', '2024-12-31']
```
**URL**
```javascript
const URL_RE = /https?:\/\/[^\s<>'"]+/g;
'Смотри https://developer.mozilla.org для деталей'.match(URL_RE)
// ['https://developer.mozilla.org']
```
**Slug (нижний регистр с дефисами)**
```javascript
function toSlug(str) {
return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
}
toSlug('Hello World! 42') // 'hello-world-42'
```
**Camel в snake**
```javascript
function camelToSnake(str) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase();
}
camelToSnake('myVariableName') // 'my_variable_name'
```
**Маска кредитной карты (показать последние 4)**
```javascript
'4111 1111 1111 1234'.replace(/\d(?=\d{4})/g, '*')
// '**** **** **** 1234'
```
**Справочник флагов**
```
g global - найти все совпадения (не только первое)
i без учёта регистра
m multiline - ^ и $ совпадают с границами строк
s dotAll - . совпадает и с символами новой строки
u unicode - включает \u{...} и классы Unicode
d hasIndices - match.indices[n] даёт [start, end] для каждой группы
```
**Когда НЕ использовать regex в JavaScript**
- Парсинг HTML - используй `document.querySelector`, `DOMParser` или библиотеку
- Парсинг URL - используй `new URL(str)`
- Парсинг JSON - используй `JSON.parse`
- Строгая валидация email - regex не может обеспечить RFC 5322; используй библиотеку
Regex блестяще подходит для сканирования и преобразования обычного текста. Для структурированных форматов всегда предпочитай специализированный парсер.
Извлеките все слова из строки, начинающиеся с заглавной буквы. Верните их как массив.
function capitalWords(str) {
// ваш код здесь
}
console.log(capitalWords('Hello world, My Name is Alice')); // ['Hello', 'My', 'Name', 'Alice']
console.log(capitalWords('no capitals here')); // []
Решение
function capitalWords(str) {
return str.match(/\b[A-Z][a-zA-Z]*/g) || [];
}
Подсчитайте, сколько раз слово встречается в строке (без учёта регистра, только целые слова).
function countWord(str, word) {
// ваш код здесь
}
console.log(countWord('The cat sat on the mat. The cat is fat.', 'the')); // 3
console.log(countWord('cats and cat and cat', 'cat')); // 3 (not 'cats')
Решение
function countWord(str, word) {
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp(`\\b${escaped}\\b`, 'gi');
return (str.match(re) || []).length;
}
Извлеките все URL из строки. URL начинается с http:// или https:// и заканчивается пробельным символом или концом строки.
function extractUrls(str) {
// ваш код здесь
}
const text = 'Visit https://example.com and http://foo.org/path?q=1 for details.';
console.log(extractUrls(text));
// ['https://example.com', 'http://foo.org/path?q=1']
Решение
function extractUrls(str) {
return str.match(/https?:\/\/[^\s]+/g) || [];
}
No split tab
Настройки cookies
Мы используем необходимые cookies для работы сайта. С вашего разрешения мы также можем сохранять настройки сайта и использовать аналитические и рекламные cookies, чтобы понимать использование сайта и поддерживать развитие проекта.
* Вы всегда можете изменить свой выбор в настройках сайта.
Выберите категории cookies
Настройки аналитики
Можно отключить аналитику использования платформы. Также можно отправить в Google Analytics запрос на удаление данных об использовании этого сайта, связанных с этим браузером.
Учебный workspace
Учитесь, читая, запуская код и решая задачи.
Практикуйте программирование с пояснениями тем, упражнениями, инструментами browser IDE, проверкой regex и тренировкой печати кода в одном workspace.
Открывайте инструменты во вкладках.Упражнения, IDE-инструменты и тренажеры остаются доступными как вкладки сайта.
Переключайтесь без потери контекста.Переходите между пояснениями, кодом и утилитами, сохраняя свое место.
Используйте sidebar как карту.Левые панели содержат навигацию, настройки, файлы, libraries и управление инструментами.
PythonJavaScriptSQLite
Одна IDE, три практичных режима
Python в браузере.Запускайте небольшие скрипты, пробуйте библиотеки и тренируйте API-запросы без установки.
JavaScript для быстрых экспериментов.Проверяйте код для браузера и сравнивайте идеи рядом с учебными материалами.
SQLite для практики с данными.Открывайте обозреватель базы данных, изучайте таблицы, пишите запросы и учитесь SQL локально.
ТемаIDE
Работайте рядом в split tabs
Держите инструкции перед глазами.Откройте упражнение или справочную страницу рядом с IDE, вместо постоянных переключений.
Сравнивайте инструменты во время обучения.Размещайте проверки regex, пояснения и эксперименты с кодом рядом, когда это нужно для задачи.
Закройте split, когда закончите.Workspace вернётся к одной сфокусированной вкладке, а открытые вкладки сайта останутся доступны.
Тренажер слепой печати кода
Или просто текста
Тренажер рассчитан на физическую клавиатуру.Откройте этот раздел на ноутбуке или компьютере с широким экраном. На телефоне тренировка слепой печати не будет корректной.
Скорость: 0 зн/мин
0 слов/мин
Лучшая скорость (60с): 0 зн/мин
0 слов/мин
Ошибки: 0
Общее время: 0.0 с
Для активации режима слепого набора не подсматривайте на физическую клавиатуру.