Регулярни изрази
Проблем
Шестнайсет реда за една дата
Формулярът за резервация иска дата във вида дд.мм.гггг. Проверяваш го символ по символ:
function isDate(s) { if (s.length !== 10) { return false; } for (let i = 0; i < s.length; i++) { const c = s[i]; if (i === 2 || i === 5) { if (c !== ".") { return false; } } else if (c < "0" || c > "9") { return false; } } return true;}
console.log(isDate("05.03.2026"));console.log(isDate("5.3.2026"));console.log(isDate("05/03/2026"));truefalsefalseШестнайсет реда, а функцията проверява само формата: две цифри, точка, две цифри, точка, четири цифри. Ако искаш да приемаш и / вместо точка, или ден с една цифра, ще добавиш още толкова. Номер на телефон, имейл или регистрационен номер ще ти струват по още една такава функция.
Идеята
Шаблон вместо код
Описваш как изглежда текстът, а JavaScript проверява:
function isDate(s) { return /^\d{2}\.\d{2}\.\d{4}$/.test(s);}
console.log(isDate("05.03.2026"));console.log(isDate("5.3.2026"));console.log(isDate("05/03/2026"));truefalsefalseШаблонът се чете отляво надясно: \d е цифра, {2} значи „два пъти“, \. е точка. Такъв шаблон се казва регулярен израз (regular expression, regex).
Шаблонът стои между две наклонени черти: /.../. Това е литерал на регулярен израз, както "..." е литерал на текст. test(текст) връща true, ако намери шаблона някъде в текста. Затова шаблонът започва с ^ („началото на текста“) и завършва с $ („краят“): така трябва да покрие целия текст, от първия до последния символ.
Виж го
Как шаблонът търси
replace не проверява целия текст, а търси парчетата, които отговарят на шаблона, и ги заменя. Ето как минава по реда:
const line = "120 € на 05.03.2026, падеж 15.04.2026";const masked = line.replace(/\d{2}\.\d{2}\.\d{4}/g, "[дата]");console.log(masked);Стъпка по стъпка, с думи
- Редът е поредица от знаци, всеки със своя позиция.
- От позиция 0: „1“ и „2“ са цифри, но после идва „0“ вместо точка. Опитът пада.
- Нов опит от всеки следващ знак: „20 “, „0 “, „ €“… все неуспехи.
- От позиция 9 всичките десет части пасват: първото съвпадение.
- Търсенето продължава след съвпадението, от запетаята, и намира второто.
- replace с флаг g заменя двете съвпадения с „[дата]“.
Шаблонът се пробва от всяка позиция поред. Щом някоя част не съвпадне, опитът пада и започва нов от следващия символ. Затова 120 не мина: две цифри, а после 0 вместо точка. Когато намери съвпадение, търсенето продължава след него.
Флагът g (global) след шаблона, /.../g, казва „всички съвпадения“. Без него replace заменя само първото.
Идеята
Класове от символи
В квадратни скоби изброяваш кои символи са позволени на едно място. Регистрационен номер на кола е една или две букви, четири цифри и две букви:
const plates = ["СА1234ВХ", "В7788КК", "СА12345ВХ", "са1234вх"];for (const plate of plates) { console.log(plate, /^[А-Я]{1,2}\d{4}[А-Я]{2}$/.test(plate));}СА1234ВХ trueВ7788КК trueСА12345ВХ falseса1234вх false| Шаблон | Съвпада с един символ |
|---|---|
[АВЕ] |
А, В или Е |
[0-9], [А-Я] |
от интервала: цифра, главна буква на кирилица |
[^0-9] |
всичко освен цифра (^ в началото на скобите) |
\d |
цифра, същото като [0-9] |
\s |
интервал, табулация, нов ред |
\w |
латинска буква, цифра или _ |
. |
какъвто и да е символ (без нов ред) |
Главните букви обръщат смисъла: \D е „не цифра“, \S „не интервал“, \W „не \w“.
Внимавай с \w: в JavaScript той е само латиница.
console.log(/^\w+$/.test("Sofia"));console.log(/^\w+$/.test("София"));console.log(/^\p{L}+$/u.test("София"));console.log(/^[А-Яа-я]+$/.test("София"));truefalsetruetrueЗа текст на кирилица пиши [А-Яа-я] или \p{L} с флага u: \p{L} е буква от всеки език, а u (unicode) казва на шаблона да разбира такива класове.
По-точно, за любопитните
[А-Я]е интервал по кодовете на символите. В него влизат и руските Ы и Э, но не и Ё, която стои отделно в таблицата. За българския текст[А-Яа-я]стига. Ако те интересува и „ѝ“, добави го:[А-Яа-яѝЍ].- Истинските български номера ползват само 12 букви, които изглеждат еднакво на кирилица и латиница:
[АВЕКМНОРСТУХ].
Пробвай
Точката
Някой е забравил обратната черта пред точките:
Какво ще отпечата програмата?
const pattern = /^\d{2}.\d{2}.\d{4}$/;console.log(pattern.test("05.03.2026"));console.log(pattern.test("05-03-2026"));console.log(pattern.test("05.03.26"));Без \ точката значи „какъвто и да е символ“, затова тирето също минава и 05-03-2026 е вярно. Третият текст има само две цифри за година, а шаблонът иска четири. Проверката изглежда, че работи, докато не дойде грешен вход. Затова пробвай шаблона и с текстове, които не трябва да минат.
Идеята
Колко пъти
След символ или клас казваш колко пъти може да се повтори. Това е квантификатор:
| Квантификатор | Значи |
|---|---|
? |
нула или едно (може да го има, може да го няма) |
* |
нула или повече |
+ |
едно или повече |
{4} |
точно 4 |
{1,2} |
от 1 до 2 |
{2,} |
2 или повече |
Мобилен номер започва с 0 или +359, после 87, 88 или 89 и още 7 цифри:
const mobile = /^(\+359|0)8[789]\d{7}$/;console.log(mobile.test("0888123456"));console.log(mobile.test("+359877654321"));console.log(mobile.test("0888 123 456"));console.log(mobile.test("088812345"));truetruefalsefalse| значи „или“. Скобите държат \+359|0 заедно: без тях „или“ би разделило целия шаблон на две. Скобите правят още неща, които ще видим в следващия урок. + в началото е със \, защото иначе е квантификатор.
Квантификаторът важи и за цяла група в скоби. Цена с дробна част или без нея:
const price = /^\d+([.,]\d{1,2})? €$/;console.log(price.test("12 €"));console.log(price.test("12.5 €"));console.log(price.test("12,50 €"));console.log(price.test(".50 €"));truetruetruefalse([.,]\d{1,2})? значи: „точка или запетая и една-две цифри“, а целият този блок може да го няма.
Идеята
Литерал или низ
В литерала /.../ обратната черта е само за шаблона: \d е цифра, \. е точка, без удвояване.
Понякога шаблонът се сглобява от текст, например когато част от него идва от променлива. Тогава е низ в кавички за new RegExp(...), и минава през два слоя: първо JavaScript чете низа, после шаблона. В низ \ започва специален символ: \n е нов ред, \" е кавичка. \d не е такъв, и JavaScript просто маха обратната черта, без грешка и без предупреждение:
const date = "05.03.2026";const pattern = new RegExp("^\d{2}\.\d{2}\.\d{4}$");console.log(pattern.source);console.log(pattern.test(date));^d{2}.d{2}.d{4}$falsesource показва шаблона, който е получил RegExp: d{2} значи „две букви d“. Затова в низ всяко \ от шаблона се пише \\:
const date = "05.03.2026";console.log(/^\d{2}\.\d{2}\.\d{4}$/.test(date));console.log(new RegExp("^\\d{2}\\.\\d{2}\\.\\d{4}$").test(date));console.log(/^C:\\Temp$/.test("C:\\Temp"));truetruetrue| Искаш в шаблона | Литерал | Низ за new RegExp |
|---|---|---|
\d (цифра) |
/\d/ |
"\\d" |
\. (истинска точка) |
/\./ |
"\\." |
\\ (истинска обратна черта) |
/\\/ |
"\\\\" |
В литерала и / е със \: /\//, иначе би затворила шаблона.
Символите, които трябва да са със \, когато ги искаш буквално, са . * + ? ( ) [ ] { } | ^ $ \.
Пробвай
Намери грешката
0 € и 0 центаИзбери реда с грешката.
split с /.../ приема регулярен израз. /./ е „всеки символ“, затова всеки символ е разделител и между тях няма нищо: частите са шест празни текста. Number("") е 0, а не грешка, затова програмата не спира, а печата грешна сума. Поправката е split(/\./). А тук шаблон изобщо не трябва: price.split(".") търси обикновен текст.const price = "12.50";console.log(price.split("."));console.log(price.split(/./));[ '12', '50' ][ '', '', '', '', '', '' ]Идеята
Замяна и разделяне по шаблон
replace и split приемат шаблон. Така чистиш и разбиваш текст, който хората пишат както им падне:
const address = "Доставка: ул. Шипка 12, вход Б";console.log(address.replace(/\s+/g, " "));const items = "хляб, мляко;кафе чай".split(/[,;\s]+/);console.log(`${items.length}: ${items.join(" | ")}`);console.log("Код SUMMER10, код summer25".replace(/summer\d+/gi, "***"));Доставка: ул. Шипка 12, вход Б4: хляб | мляко | кафе | чайКод ***, код ***\s+е „един или повече интервала“: всяка група интервали става един.[,;\s]+е „запетаи, точки и запетаи или интервали, колкото и да са“: разделителят може да е всеки от тях.
iслед шаблона е флаг: шаблонът не гледа главни и малки букви. Флаговете се пишат един до друг:giе „всички съвпадения, без значение от главните букви“.
replace с текст вместо шаблон заменя само първата поява. За всички е replaceAll("стар", "нов"). Ако не ти трябва шаблон, ползвай текст: не трябва да мислиш за точки и обратни черти.
\b е граница на дума: мястото между буква и нещо, което не е буква. С него заменяш само цели думи. Но в JavaScript \b, като \w, познава само латиница, дори с флаг u:
const text = "стар и старо";console.log(text.replace(/стар/g, "нов"));console.log(text.replace(/\bстар\b/g, "нов"));console.log(text.replace(/(?<!\p{L})стар(?!\p{L})/gu, "нов"));нов и новостар и старонов и староБез \b „старо“ става „ново“. С \b JavaScript не вижда кирилицата като букви и не заменя нищо. За кирилица границата се пише с поглед (lookaround): (?<!\p{L}) е „отпред няма буква“, а (?!\p{L}) е „отзад няма буква“. Те не взимат символи, само проверяват.
По-точно, за любопитните
\Bе обратното на\b: място вътре в дума. И той познава само латиница, затова на кирилица намира „стар“ и в „старо“. Само в средата на дума е(?<=\p{L})старс флагu: „отпред има буква“.^е началото на текста, а$краят:text.replace(/^0+/, "")маха водещите нули.- Флаговете са букви след шаблона:
g(всички),i(без значение от главните букви),u(unicode),mиs(в следващия урок).(?i)в началото на шаблона в Node 22 е грешка. "a|b".split(/|/)е капан като точката:|е „или“ и шаблонът е празен, затова дели между всеки два символа."a|b".split("|")търси обикновен текст и няма този проблем.
console.log("застаря старо".match(/\Bстар/g));console.log("застаря старо".match(/(?<=\p{L})стар/gu));console.log("a|b".split(/|/));console.log("a|b".split("|"));console.log("a a".replace("a", "b"), "a a".replaceAll("a", "b"));[ 'стар', 'стар' ][ 'стар' ][ 'a', '|', 'b' ][ 'a', 'b' ]b a b bПробвай
Пощенски код
Българският пощенски код е точно четири цифри. Какво липсва, за да отпечата програмата true за "1000" и false за "100" или "10000"?
const code = "1000";
console.log(/^$/.test(code));\d{4}: точно четири цифри. Понеже шаблонът е между ^ и $, три или пет цифри не минават.Упражнение
Проверки с шаблон
Реши задачите в редактора си. Чети редове, докато има, така (засега го копирай):
import { createInterface } from "node:readline";
const rl = createInterface({ input: process.stdin });for await (const line of rl) { // line е поредният ред, без "\n"}В урока „Файлове“ ще четеш същите редове от файл.
1. Дата. Провери всяка дата дали е във вида дд/мм/гггг. Денят е от 01 до 31, а месецът от 01 до 12. Подсказка: денят е 0[1-9] или [12]\d, или 3[01]. В литерала / се пише \/.
01/01/202032/01/202015/05/202101/01/2020 - Валидна32/01/2020 - Невалидна15/05/2021 - Валидна2. Мобилен номер. Хората пишат номера с интервали и тирета. Махни ги с replace и провери това, което остава, с шаблона за мобилен номер от урока.
0888 123 456+359 87 765 432102 812 34560899-12-3450888 123 456 - валиден+359 87 765 4321 - валиден02 812 3456 - невалиден0899-12-345 - невалиден3. Имейл. Провери дали всеки ред прилича на имейл: една или повече латински букви, цифри, ., + или -, после @, после име на домейн с поне една точка (firma.bg, mail.firma.bg).
elena.koleva@firma.bgelena@firma@firma.bgelena koleva@firma.bgelena.koleva@firma.bg - даelena@firma - не@firma.bg - неelena koleva@firma.bg - не4. Списък за пазаруване. Продуктите са разделени със запетаи, точки и запетаи, вертикални черти или интервали, понякога по няколко наведнъж. Отпечатай ги номерирани.
хляб, мляко;кафе чай|сирене1. хляб2. мляко3. кафе4. чай5. сиренеПо-точно, за любопитните
- Шаблонът за дата приема и
31/02/2026. Дали денят съществува в месеца, regex не може да каже разумно. Шаблонът е само първото сито. ВградениятDateтук не помага: той не отказва 31 февруари, а го прехвърля в 3 март (месеците вDateсе броят от 0, затова февруари е 1):
console.log(new Date(2026, 1, 31).toDateString());Tue Mar 03 2026Затова провери деня сам: построй датата и виж дали месецът е останал същият.
- Пълното правило за имейл адрес (RFC 5322) позволява кавички, коментари и интервали в определени места. Шаблон, който го покрива изцяло, е стотици символи и никой не го поддържа. Истинската проверка е да изпратиш писмо с връзка за потвърждение.
Още задачи
- Парола. Провери дали паролата е поне 8 символа и има поне една цифра. Направи го с два
test:/^.{8,}$/и/\d/. Защо на втория не ти трябват^и$? - Час. Провери час във вида
чч:ммот00:00до23:59. - Само цифри. От
"Тел.: +359 (88) 812-34-56"остави само цифрите и+с единreplaceи[^...].
Тест