Тебе нужно извлечь все суммы из строки «Итого: 1500₽, скидка 10%, налог 150₽» — только рублёвые, не проценты. Или найти версию в строке «node v18.12, npm v9.5» — числа после «v», но не все числа. Или распарсить конфиг-файл по строкам «key=value». Для всего этого нужны lookahead, lookbehind и именованные группы.
Обычные regex-паттерны не умеют «смотреть вперёд» или «назад» без захвата этого контекста. Lookahead/lookbehind позволяют добавить условия на окружение без включения его в результат. Именованные группы делают код читаемым.
.match(), .test().replace(), .split() с регулярными выражениями.exec() в цикле// X(?=Y) — позитивный: X только если ЗА НИМ следует Y
// X(?!Y) — негативный: X только если ЗА НИМ НЕ следует Y
// Только числа перед ₽ или $:
const priceRe = /\d+(?=[₽$])/g
'100₽ 200$ 50% 300руб'.match(priceRe) // ['100', '200'] — без 50 и 300!
// Слова не перед двоеточием (не ключи объекта):
const wordRe = /\w+(?!:)/g// (?<=Y)X — позитивный: X только если ПЕРЕД НИМ стоит Y
// (?<!Y)X — негативный: X только если ПЕРЕД НИМ НЕ стоит Y
// Числа после знака валюты $:
const afterDollarRe = /(?<=\$)\d+(\.\d{2})?/g
'$99.99 €200 $150'.match(afterDollarRe) // ['99.99', '150']
// Версии после "v":
const versionRe = /(?<=v)\d+\.\d+/g
'node v18.12 npm v9.5'.match(versionRe) // ['18.12', '9.5']// (?<name>...) — захват с именем, доступен через match.groups.name
// (?:...) — группа без захвата (только для группировки)
// Парсинг даты ISO
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const match = '2024-03-15'.match(dateRe)
const { year, month, day } = match.groups
// year='2024', month='03', day='15'
// Деструктуризация прямо из groups// \1 — ссылка на первую группу захвата (в самом regex)
// \k<name> — ссылка на именованную группу
// Поиск удвоенных слов:
const doubled = /\b(\w+) \1\b/gi
'the the cat sat sat'.match(doubled) // ['the the', 'sat sat']
// Совпадающие кавычки:
const quoted = /(['"]).*?\1/g// Жадный * : захватывает максимально
// Нежадный *?: захватывает минимально
const html = '<b>жирный</b> и <i>курсив</i>'
html.match(/<.+>/g) // ['<b>жирный</b> и <i>курсив</i>'] — ВСЁ!
html.match(/<.+?>/g) // ['<b>', '</b>', '<i>', '</i>'] — отдельно
// Аналогично: +? ?? {n,m}?// g — global: все совпадения (не только первое)
// i — case-insensitive
// m — multiline: ^ и $ работают с переносами строк
// s — dotAll: . соответствует переносам строк
// u — unicode: поддержка Unicode (нужен для эмодзи)
// d — indices: добавляет .indices с позициями групп// В регулярном выражении /\d+/ — один слеш
// В строке (шаблонной или обычной) для передачи в RegExp():
const re = new RegExp('\\d+') // \\d → \d в строке → \d в regexОшибка 1: Флаг g без .exec() в цикле
// НЕВЕРНО — с флагом g и .match() именованные группы теряются
const re = /(?<year>\d{4})/g
'2023 2024'.match(re) // ['2023', '2024'] — groups нет!
// ВЕРНО — .exec() в цикле сохраняет groups
let m
while ((m = re.exec('2023 2024')) !== null) {
console.log(m.groups.year) // '2023', '2024'
}Ошибка 2: Забыть сбросить .lastIndex
const re = /\d+/g
re.exec('hello 42') // { index: 6, ... }
re.exec('1 2 3') // null! lastIndex=8 не совпал в новой строке
// ВЕРНО — создавай новый regex или сбрасывай:
re.lastIndex = 0Ошибка 3: Жадный квантификатор для HTML
// НЕВЕРНО — захватывает слишком много
'<div>раз</div> <div>два</div>'.match(/<div>.*<\/div>/g)
// ['<div>раз</div> <div>два</div>'] — ВСЁ как одно совпадение!
// ВЕРНО — нежадный
'<div>раз</div> <div>два</div>'.match(/<div>.*?<\/div>/g)
// ['<div>раз</div>', '<div>два</div>']{{переменных}} в шаблонах.env, nginx.conf, package.json scriptsLookahead/lookbehind: извлечение цен и версий, именованные группы для дат и URL, парсинг конфига
// Продвинутые регулярные выражения
// ===== Lookahead =====
console.log('=== Lookahead: числа перед символом =====')
// Числа перед ₽ или $
const priceRe = /\d+(\.\d+)?(?=[₽$])/g
const priceStr = 'Яблоки 45₽, скидка 10%, USB-кабель $12, итого 57₽'
console.log('Строка:', priceStr)
console.log('Цены (₽/$):', priceStr.match(priceRe)) // ['45', '12', '57']
// Негативный lookahead: числа НЕ перед %
const noPercent = /\d+(?!%)/g
const discountStr = '20% скидка на 500₽ товары, 3% кэшбек'
const amounts = discountStr.match(/\d+(?=[₽$])/g)
console.log('Суммы (не проценты):', amounts) // ['500']
// ===== Lookbehind =====
console.log('\n=== Lookbehind: числа после символа ===')
// Суммы после $
const afterDollar = /(?<=\$)\d+(\.\d{2})?/g
const invoice = 'Subtotal: $99.99, Tax: $8.50, Total: $108.49'
console.log('Инвойс:', invoice)
console.log('Суммы:', invoice.match(afterDollar)) // ['99.99', '8.50', '108.49']
// Версии после "v"
const versionRe = /(?<=v)\d+\.\d+(\.\d+)?/gi
const releases = 'Node.js v20.5.0, Python v3.11, Chrome v118.0'
console.log('\nВерсии:', releases.match(versionRe)) // ['20.5.0', '3.11', '118.0']
// ===== Именованные группы =====
console.log('\n=== Именованные группы ===')
// ISO дата
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const dateStr = '2024-03-15'
const dateMatch = dateStr.match(dateRe)
if (dateMatch?.groups) {
const { year, month, day } = dateMatch.groups
console.log('Дата:', { year, month, day })
}
// URL разбор
const urlRe = /(?<protocol>https?):\/\/(?<host>[^/?#]+)(?<path>\/[^?#]*)?(?:\?(?<query>[^#]*))?/
const urlStr = 'https://api.example.com/users/profile?page=1&limit=20'
const urlMatch = urlStr.match(urlRe)
if (urlMatch?.groups) {
const { protocol, host, path, query } = urlMatch.groups
console.log('\nURL разбор:')
console.log(' protocol:', protocol) // https
console.log(' host: ', host) // api.example.com
console.log(' path: ', path) // /users/profile
console.log(' query: ', query) // page=1&limit=20
}
// ===== Парсинг конфига ===
console.log('\n=== Парсинг key=value конфига ===')
function parseConfig(text) {
const result = {}
const lineRe = /^(?<key>[\w.]+)\s*=\s*(?<value>.+?)\s*$/gm
let m
while ((m = lineRe.exec(text)) !== null) {
const { key, value } = m.groups
result[key] = value
}
return result
}
const envText = `
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_prod
API_KEY=abc123xyz
API_TIMEOUT=30
DEBUG=false
`
const config = parseConfig(envText)
console.log('DB_HOST:', config.DB_HOST) // localhost
console.log('DB_PORT:', config.DB_PORT) // 5432
console.log('API_KEY:', config.API_KEY) // abc123xyz
console.log('DEBUG:', config.DEBUG) // false
// ===== Нежадные квантификаторы ===
console.log('\n=== Жадный vs нежадный ===')
const html = '<b>жирный</b> и <i>курсив</i>'
const greedy = html.match(/<.+>/g)
const lazy = html.match(/<.+?>/g)
console.log('Жадный (<.+>):', greedy) // 1 матч — всё
console.log('Нежадный (<.+?>):', lazy) // 4 тега отдельно
// ===== Обратные ссылки ===
console.log('\n=== Обратные ссылки: удвоенные слова ===')
const doubleWord = /\b(\w+)\s+\1\b/gi
const text = 'Это это тест. Ошибка ошибка часто бывает. All all good here.'
const dups = []
let m2
while ((m2 = doubleWord.exec(text)) !== null) {
dups.push(m2[0])
}
console.log('Удвоения:', dups)Тебе нужно извлечь все суммы из строки «Итого: 1500₽, скидка 10%, налог 150₽» — только рублёвые, не проценты. Или найти версию в строке «node v18.12, npm v9.5» — числа после «v», но не все числа. Или распарсить конфиг-файл по строкам «key=value». Для всего этого нужны lookahead, lookbehind и именованные группы.
Обычные regex-паттерны не умеют «смотреть вперёд» или «назад» без захвата этого контекста. Lookahead/lookbehind позволяют добавить условия на окружение без включения его в результат. Именованные группы делают код читаемым.
.match(), .test().replace(), .split() с регулярными выражениями.exec() в цикле// X(?=Y) — позитивный: X только если ЗА НИМ следует Y
// X(?!Y) — негативный: X только если ЗА НИМ НЕ следует Y
// Только числа перед ₽ или $:
const priceRe = /\d+(?=[₽$])/g
'100₽ 200$ 50% 300руб'.match(priceRe) // ['100', '200'] — без 50 и 300!
// Слова не перед двоеточием (не ключи объекта):
const wordRe = /\w+(?!:)/g// (?<=Y)X — позитивный: X только если ПЕРЕД НИМ стоит Y
// (?<!Y)X — негативный: X только если ПЕРЕД НИМ НЕ стоит Y
// Числа после знака валюты $:
const afterDollarRe = /(?<=\$)\d+(\.\d{2})?/g
'$99.99 €200 $150'.match(afterDollarRe) // ['99.99', '150']
// Версии после "v":
const versionRe = /(?<=v)\d+\.\d+/g
'node v18.12 npm v9.5'.match(versionRe) // ['18.12', '9.5']// (?<name>...) — захват с именем, доступен через match.groups.name
// (?:...) — группа без захвата (только для группировки)
// Парсинг даты ISO
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const match = '2024-03-15'.match(dateRe)
const { year, month, day } = match.groups
// year='2024', month='03', day='15'
// Деструктуризация прямо из groups// \1 — ссылка на первую группу захвата (в самом regex)
// \k<name> — ссылка на именованную группу
// Поиск удвоенных слов:
const doubled = /\b(\w+) \1\b/gi
'the the cat sat sat'.match(doubled) // ['the the', 'sat sat']
// Совпадающие кавычки:
const quoted = /(['"]).*?\1/g// Жадный * : захватывает максимально
// Нежадный *?: захватывает минимально
const html = '<b>жирный</b> и <i>курсив</i>'
html.match(/<.+>/g) // ['<b>жирный</b> и <i>курсив</i>'] — ВСЁ!
html.match(/<.+?>/g) // ['<b>', '</b>', '<i>', '</i>'] — отдельно
// Аналогично: +? ?? {n,m}?// g — global: все совпадения (не только первое)
// i — case-insensitive
// m — multiline: ^ и $ работают с переносами строк
// s — dotAll: . соответствует переносам строк
// u — unicode: поддержка Unicode (нужен для эмодзи)
// d — indices: добавляет .indices с позициями групп// В регулярном выражении /\d+/ — один слеш
// В строке (шаблонной или обычной) для передачи в RegExp():
const re = new RegExp('\\d+') // \\d → \d в строке → \d в regexОшибка 1: Флаг g без .exec() в цикле
// НЕВЕРНО — с флагом g и .match() именованные группы теряются
const re = /(?<year>\d{4})/g
'2023 2024'.match(re) // ['2023', '2024'] — groups нет!
// ВЕРНО — .exec() в цикле сохраняет groups
let m
while ((m = re.exec('2023 2024')) !== null) {
console.log(m.groups.year) // '2023', '2024'
}Ошибка 2: Забыть сбросить .lastIndex
const re = /\d+/g
re.exec('hello 42') // { index: 6, ... }
re.exec('1 2 3') // null! lastIndex=8 не совпал в новой строке
// ВЕРНО — создавай новый regex или сбрасывай:
re.lastIndex = 0Ошибка 3: Жадный квантификатор для HTML
// НЕВЕРНО — захватывает слишком много
'<div>раз</div> <div>два</div>'.match(/<div>.*<\/div>/g)
// ['<div>раз</div> <div>два</div>'] — ВСЁ как одно совпадение!
// ВЕРНО — нежадный
'<div>раз</div> <div>два</div>'.match(/<div>.*?<\/div>/g)
// ['<div>раз</div>', '<div>два</div>']{{переменных}} в шаблонах.env, nginx.conf, package.json scriptsLookahead/lookbehind: извлечение цен и версий, именованные группы для дат и URL, парсинг конфига
// Продвинутые регулярные выражения
// ===== Lookahead =====
console.log('=== Lookahead: числа перед символом =====')
// Числа перед ₽ или $
const priceRe = /\d+(\.\d+)?(?=[₽$])/g
const priceStr = 'Яблоки 45₽, скидка 10%, USB-кабель $12, итого 57₽'
console.log('Строка:', priceStr)
console.log('Цены (₽/$):', priceStr.match(priceRe)) // ['45', '12', '57']
// Негативный lookahead: числа НЕ перед %
const noPercent = /\d+(?!%)/g
const discountStr = '20% скидка на 500₽ товары, 3% кэшбек'
const amounts = discountStr.match(/\d+(?=[₽$])/g)
console.log('Суммы (не проценты):', amounts) // ['500']
// ===== Lookbehind =====
console.log('\n=== Lookbehind: числа после символа ===')
// Суммы после $
const afterDollar = /(?<=\$)\d+(\.\d{2})?/g
const invoice = 'Subtotal: $99.99, Tax: $8.50, Total: $108.49'
console.log('Инвойс:', invoice)
console.log('Суммы:', invoice.match(afterDollar)) // ['99.99', '8.50', '108.49']
// Версии после "v"
const versionRe = /(?<=v)\d+\.\d+(\.\d+)?/gi
const releases = 'Node.js v20.5.0, Python v3.11, Chrome v118.0'
console.log('\nВерсии:', releases.match(versionRe)) // ['20.5.0', '3.11', '118.0']
// ===== Именованные группы =====
console.log('\n=== Именованные группы ===')
// ISO дата
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const dateStr = '2024-03-15'
const dateMatch = dateStr.match(dateRe)
if (dateMatch?.groups) {
const { year, month, day } = dateMatch.groups
console.log('Дата:', { year, month, day })
}
// URL разбор
const urlRe = /(?<protocol>https?):\/\/(?<host>[^/?#]+)(?<path>\/[^?#]*)?(?:\?(?<query>[^#]*))?/
const urlStr = 'https://api.example.com/users/profile?page=1&limit=20'
const urlMatch = urlStr.match(urlRe)
if (urlMatch?.groups) {
const { protocol, host, path, query } = urlMatch.groups
console.log('\nURL разбор:')
console.log(' protocol:', protocol) // https
console.log(' host: ', host) // api.example.com
console.log(' path: ', path) // /users/profile
console.log(' query: ', query) // page=1&limit=20
}
// ===== Парсинг конфига ===
console.log('\n=== Парсинг key=value конфига ===')
function parseConfig(text) {
const result = {}
const lineRe = /^(?<key>[\w.]+)\s*=\s*(?<value>.+?)\s*$/gm
let m
while ((m = lineRe.exec(text)) !== null) {
const { key, value } = m.groups
result[key] = value
}
return result
}
const envText = `
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_prod
API_KEY=abc123xyz
API_TIMEOUT=30
DEBUG=false
`
const config = parseConfig(envText)
console.log('DB_HOST:', config.DB_HOST) // localhost
console.log('DB_PORT:', config.DB_PORT) // 5432
console.log('API_KEY:', config.API_KEY) // abc123xyz
console.log('DEBUG:', config.DEBUG) // false
// ===== Нежадные квантификаторы ===
console.log('\n=== Жадный vs нежадный ===')
const html = '<b>жирный</b> и <i>курсив</i>'
const greedy = html.match(/<.+>/g)
const lazy = html.match(/<.+?>/g)
console.log('Жадный (<.+>):', greedy) // 1 матч — всё
console.log('Нежадный (<.+?>):', lazy) // 4 тега отдельно
// ===== Обратные ссылки ===
console.log('\n=== Обратные ссылки: удвоенные слова ===')
const doubleWord = /\b(\w+)\s+\1\b/gi
const text = 'Это это тест. Ошибка ошибка часто бывает. All all good here.'
const dups = []
let m2
while ((m2 = doubleWord.exec(text)) !== null) {
dups.push(m2[0])
}
console.log('Удвоения:', dups)Реализуй три функции с продвинутыми regex. - `extractPrices(str)` — извлекает все числа перед `$` или `₽` через lookahead. Возвращает массив чисел - `validatePassword(str)` — проверяет: минимум 8 символов, есть заглавная буква, есть цифра, есть спецсимвол. Возвращает `{ valid, checks }` - `parseConfig(str)` — парсит строки `"key=value"` через именованные группы (`(?<key>...)`, `(?<value>...)`). Возвращает объект
extractPrices: /\d+(\.\d+)?(?=[$₽])/g, match().map(Number). validatePassword: отдельный regex.test(str) для каждого условия. parseConfig: /^(?<key>[\w.]+)\s*=\s*(?<value>.+?)$/gm, re.exec(str) в цикле, result[m.groups.key] = m.groups.value