Разгледайте напредналото съпоставяне на шаблони в JavaScript с регулярни изрази. Научете за синтаксиса, практическите приложения и техниките за оптимизация.
JavaScript съпоставяне на шаблони с регулярни изрази: Пълно ръководство
Регулярните изрази (regex) са мощен инструмент за съпоставяне на шаблони и обработка на текст в JavaScript. Те позволяват на разработчиците да търсят, валидират и трансформират низове въз основа на определени шаблони. Това ръководство предоставя цялостен преглед на регулярните изрази в JavaScript, като обхваща синтаксис, употреба и напреднали техники.
Какво представляват регулярните изрази?
Регулярният израз е последователност от символи, които дефинират шаблон за търсене. Тези шаблони се използват за съпоставяне и обработка на низове. Регулярните изрази се използват широко в програмирането за задачи като:
- Валидиране на данни: Гарантиране, че въведените от потребителя данни съответстват на определени формати (напр. имейл адреси, телефонни номера).
- Извличане на данни: Получаване на специфична информация от текст (напр. извличане на дати, URL адреси или цени).
- Търсене и замяна: Намиране и замяна на текст въз основа на сложни шаблони.
- Обработка на текст: Разделяне, обединяване или трансформиране на низове въз основа на определени правила.
Създаване на регулярни изрази в JavaScript
В JavaScript регулярните изрази могат да бъдат създадени по два начина:
- Използване на литерал за регулярен израз: Поставете шаблона между наклонени черти (
/). - Използване на конструктора
RegExp: Създайте обектRegExpс шаблона като низ.
Пример:
// Използване на литерал за регулярен израз
const regexLiteral = /hello/;
// Използване на конструктора RegExp
const regexConstructor = new RegExp("hello");
Изборът между двата метода зависи от това дали шаблонът е известен по време на компилация или се генерира динамично. Използвайте литералната нотация, когато шаблонът е фиксиран и известен предварително. Използвайте конструктора, когато шаблонът трябва да бъде изграден програмно, особено когато се включват променливи.
Основен синтаксис на Regex
Регулярните изрази се състоят от символи, които представляват шаблона за съвпадение. Ето някои основни компоненти на regex:
- Литерални символи: Съвпадат със самите символи (напр.
/a/съвпада със символа 'a'). - Метасимволи: Имат специални значения (напр.
.,^,$,*,+,?,[],{},(),\,|). - Класове символи: Представляват множества от символи (напр.
[abc]съвпада с 'a', 'b' или 'c'). - Квантификатори: Указват колко пъти трябва да се появи даден символ или група (напр.
*,+,?,{n},{n,},{n,m}). - Котви (Anchors): Съвпадат с позиции в низа (напр.
^съвпада с началото,$съвпада с края).
Често срещани метасимволи:
.(точка): Съвпада с всеки един символ, с изключение на нов ред.^(каретка): Съвпада с началото на низа.$(долар): Съвпада с края на низа.*(звездичка): Съвпада с нула или повече срещания на предходния символ или група.+(плюс): Съвпада с едно или повече срещания на предходния символ или група.?(въпросителен знак): Съвпада с нула или едно срещане на предходния символ или група. Използва се за незадължителни символи.[](квадратни скоби): Дефинира клас от символи, съвпадащ с всеки един символ в скобите.{}(къдрави скоби): Указва броя на срещанията, които трябва да съвпаднат.{n}съвпада точно n пъти,{n,}съвпада n или повече пъти,{n,m}съвпада между n и m пъти.()(кръгли скоби): Групира символи заедно и улавя съвпадналия подниз.\(наклонена черта): Екранира метасимволи, позволявайки ви да ги съпоставите буквално.|(вертикална черта): Действа като оператор „или“, съвпадащ или с израза преди него, или с този след него.
Класове символи:
[abc]: Съвпада с всеки един от символите a, b или c.[^abc]: Съвпада с всеки символ, който *не е* a, b или c.[a-z]: Съвпада с всяка малка буква от a до z.[A-Z]: Съвпада с всяка главна буква от A до Z.[0-9]: Съвпада с всяка цифра от 0 до 9.[a-zA-Z0-9]: Съвпада с всеки буквено-цифров символ.\d: Съвпада с всяка цифра (еквивалентно на[0-9]).\D: Съвпада с всеки нецифров символ (еквивалентно на[^0-9]).\w: Съвпада с всеки „word“ символ (буквено-цифров плюс долна черта; еквивалентно на[a-zA-Z0-9_]).\W: Съвпада с всеки символ, който не е „word“ символ (еквивалентно на[^a-zA-Z0-9_]).\s: Съвпада с всеки празен символ (интервал, таб, нов ред и т.н.).\S: Съвпада с всеки символ, който не е празен.
Квантификатори:
*: Съвпада с предходния елемент нула или повече пъти. Например,a*съвпада с "", "a", "aa", "aaa" и т.н.+: Съвпада с предходния елемент един или повече пъти. Например,a+съвпада с "a", "aa", "aaa", но не и с "".?: Съвпада с предходния елемент нула или един път. Например,a?съвпада с "" или "a".{n}: Съвпада с предходния елемент точно *n* пъти. Например,a{3}съвпада с "aaa".{n,}: Съвпада с предходния елемент *n* или повече пъти. Например,a{2,}съвпада с "aa", "aaa", "aaaa" и т.н.{n,m}: Съвпада с предходния елемент между *n* и *m* пъти (включително). Например,a{2,4}съвпада с "aa", "aaa" или "aaaa".
Котви (Anchors):
^: Съвпада с началото на низа. Например,^Helloсъвпада с низове, които *започват* с "Hello".$: Съвпада с края на низа. Например,World$съвпада с низове, които *завършват* с "World".\b: Съвпада с граница на дума. Това е позицията между „word“ символ (\w) и символ, който не е „word“ (\W), или началото или края на низа. Например,\bword\bсъвпада с цялата дума "word".
Флагове (Flags):
Regex флаговете променят поведението на регулярните изрази. Те се добавят в края на regex литерала или се подават като втори аргумент на конструктора RegExp.
g(global): Съвпада с всички срещания на шаблона, а не само с първото.i(ignore case): Извършва съпоставяне без значение от регистъра (case-insensitive).m(multiline): Активира многоредов режим, където^и$съвпадат с началото и края на всеки ред (разделен с\n).s(dotAll): Позволява на точката (.) да съвпада и със символи за нов ред.u(unicode): Активира пълна поддръжка на Unicode.y(sticky): Съвпада само от индекса, указан от свойствотоlastIndexна regex-а.
JavaScript Regex методи
JavaScript предоставя няколко метода за работа с регулярни изрази:
test(): Проверява дали даден низ съвпада с шаблона. Връщаtrueилиfalse.exec(): Изпълнява търсене за съвпадение в низ. Връща масив, съдържащ съвпадналия текст и уловените групи, илиnull, ако не е намерено съвпадение.match(): Връща масив, съдържащ резултатите от съпоставянето на низ с регулярен израз. Поведението му се различава със и без флагаg.search(): Проверява за съвпадение в низ. Връща индекса на първото съвпадение или -1, ако не е намерено съвпадение.replace(): Заменя срещанията на шаблон със заместващ низ или с функция, която връща заместващия низ.split(): Разделя низ на масив от поднизове въз основа на регулярен израз.
Примери, използващи Regex методи:
// test()
const regex = /hello/;
const str = "hello world";
console.log(regex.test(str)); // Изход: true
// exec()
const regex2 = /hello (\w+)/;
const str2 = "hello world";
const result = regex2.exec(str2);
console.log(result); // Изход: ["hello world", "world", index: 0, input: "hello world", groups: undefined]
// match() с флаг 'g'
const regex3 = /\d+/g; // Съвпада с една или повече цифри глобално
const str3 = "There are 123 apples and 456 oranges.";
const matches = str3.match(regex3);
console.log(matches); // Изход: ["123", "456"]
// match() без флаг 'g'
const regex4 = /\d+/;
const str4 = "There are 123 apples and 456 oranges.";
const match = str4.match(regex4);
console.log(match); // Изход: ["123", index: 11, input: "There are 123 apples and 456 oranges.", groups: undefined]
// search()
const regex5 = /world/;
const str5 = "hello world";
console.log(str5.search(regex5)); // Изход: 6
// replace()
const regex6 = /world/;
const str6 = "hello world";
const newStr = str6.replace(regex6, "JavaScript");
console.log(newStr); // Изход: hello JavaScript
// replace() с функция
const regex7 = /(\d+)-(\d+)-(\d+)/;
const str7 = "Today's date is 2023-10-27";
const newStr2 = str7.replace(regex7, (match, year, month, day) => {
return `${day}/${month}/${year}`;
});
console.log(newStr2); // Изход: Today's date is 27/10/2023
// split()
const regex8 = /, /;
const str8 = "apple, banana, cherry";
const arr = str8.split(regex8);
console.log(arr); // Изход: ["apple", "banana", "cherry"]
Напреднали Regex техники
Улавящи групи (Capturing Groups):
Кръглите скоби () се използват за създаване на улавящи групи в регулярните изрази. Уловените групи ви позволяват да извлечете специфични части от съвпадналия текст. Методите exec() и match() връщат масив, където първият елемент е цялото съвпадение, а следващите елементи са уловените групи.
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match[0]); // Изход: 2023-10-27 (Цялото съвпадение)
console.log(match[1]); // Изход: 2023 (Първата уловена група - година)
console.log(match[2]); // Изход: 10 (Втората уловена група - месец)
console.log(match[3]); // Изход: 27 (Третата уловена група - ден)
Именувани улавящи групи (Named Capturing Groups):
ES2018 въведе именувани улавящи групи, които ви позволяват да присвоявате имена на улавящи групи, използвайки синтаксиса (?. Това прави кода по-четлив и лесен за поддръжка.
const regex = /(?\d{4})-(?\d{2})-(?\d{2})/;
const dateString = "2023-10-27";
const match = regex.exec(dateString);
console.log(match.groups.year); // Изход: 2023
console.log(match.groups.month); // Изход: 10
console.log(match.groups.day); // Изход: 27
Неулавящи групи (Non-Capturing Groups):
Ако трябва да групирате части от regex, без да ги улавяте (напр. за прилагане на квантификатор към група), можете да използвате неулавяща група със синтаксиса (?:...). Това избягва ненужното заделяне на памет за уловени групи.
const regex = /(?:https?:\/\/)?([\w\.]+)/; // Съвпада с URL, но улавя само името на домейна
const url = "https://www.example.com/path";
const match = regex.exec(url);
console.log(match[1]); // Изход: www.example.com
Lookarounds:
Lookarounds са твърдения с нулева ширина, които съвпадат с позиция в низ въз основа на шаблон, който предхожда (lookbehind) или следва (lookahead) тази позиция, без да включват самия lookaround шаблон в съвпадението.
- Положителен Lookahead:
(?=...)Съвпада, ако шаблонът вътре в lookahead *следва* текущата позиция. - Отрицателен Lookahead:
(?!...)Съвпада, ако шаблонът вътре в lookahead *не следва* текущата позиция. - Положителен Lookbehind:
(?<=...)Съвпада, ако шаблонът вътре в lookbehind *предхожда* текущата позиция. - Отрицателен Lookbehind:
(? Съвпада, ако шаблонът вътре в lookbehind *не предхожда* текущата позиция.
Пример:
// Положителен Lookahead: Вземи цената, само когато е последвана от USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // Изход: ["100"]
// Отрицателен Lookahead: Вземи думата, само когато не е последвана от число
const regex2 = /\b\w+\b(?! \d)/;
const text2 = "apple 123 banana orange 456";
const matches = text2.match(regex2);
console.log(matches); // Изход: null, защото match() връща само първото съвпадение без флаг 'g', което не е това, от което се нуждаем.
// за да го поправим:
const regex3 = /\b\w+\b(?! \d)/g;
const text3 = "apple 123 banana orange 456";
const matches3 = text3.match(regex3);
console.log(matches3); // Изход: [ 'banana' ]
// Положителен Lookbehind: Вземи стойността, само когато е предшествана от $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // Изход: ["200"]
// Отрицателен Lookbehind: Вземи думата, само когато не е предшествана от думата 'not'
const regex5 = /(?
Обратни препратки (Backreferences):
Обратните препратки ви позволяват да се обръщате към предишно уловени групи в рамките на същия регулярен израз. Те използват синтаксиса \1, \2 и т.н., където числото съответства на номера на уловената група.
const regex = /([a-z]+) \1/;
const text = "hello hello world";
const match = regex.exec(text);
console.log(match); // Изход: ["hello hello", "hello", index: 0, input: "hello hello world", groups: undefined]
Практически приложения на регулярните изрази
Валидиране на имейл адреси:
Често срещан случай на употреба на регулярни изрази е валидирането на имейл адреси. Въпреки че перфектният regex за валидиране на имейли е изключително сложен, ето един опростен пример:
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
console.log(emailRegex.test("test@example.com")); // Изход: true
console.log(emailRegex.test("invalid-email")); // Изход: false
console.log(emailRegex.test("test@sub.example.co.uk")); // Изход: true
Извличане на URL адреси от текст:
Можете да използвате регулярни изрази за извличане на URL адреси от текстов блок:
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
const text = "Visit our website at https://www.example.com or check out http://blog.example.org.";
const urls = text.match(urlRegex);
console.log(urls); // Изход: ["https://www.example.com", "http://blog.example.org"]
Парсване на CSV данни:
Регулярните изрази могат да се използват за парсване на CSV (Comma-Separated Values) данни. Ето пример за разделяне на CSV низ в масив от стойности, като се обработват полета в кавички:
const csvString = 'John,Doe,"123, Main St",New York';
const csvRegex = /(?:"([^"]*(?:""[^"]*)*)")|([^,]+)/g; //Коригиран CSV regex
let values = [];
let match;
while (match = csvRegex.exec(csvString)) {
values.push(match[1] ? match[1].replace(/""/g, '"') : match[2]);
}
console.log(values); // Изход: ["John", "Doe", "123, Main St", "New York"]
Валидация на международен телефонен номер
Валидирането на международни телефонни номера е сложно поради различните формати и дължини. Стабилното решение често включва използването на библиотека, но опростен regex може да осигури основна валидация:
const phoneRegex = /^\+(?:[0-9] ?){6,14}[0-9]$/;
console.log(phoneRegex.test("+1 555 123 4567")); // Изход: true (Пример за САЩ)
console.log(phoneRegex.test("+44 20 7946 0500")); // Изход: true (Пример за Великобритания)
console.log(phoneRegex.test("+81 3 3224 5000")); // Изход: true (Пример за Япония)
console.log(phoneRegex.test("123-456-7890")); // Изход: false
Валидация на силата на паролата
Регулярните изрази са полезни за налагане на политики за сила на паролата. Примерът по-долу проверява за минимална дължина, главна буква, малка буква и число.
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
console.log(passwordRegex.test("P@ssword123")); // Изход: true
console.log(passwordRegex.test("password")); // Изход: false (няма главна буква или число)
console.log(passwordRegex.test("Password")); // Изход: false (няма число)
console.log(passwordRegex.test("Pass123")); // Изход: false (няма малка буква)
console.log(passwordRegex.test("P@ss1")); // Изход: false (по-малко от 8 символа)
Техники за оптимизация на Regex
Регулярните изрази могат да бъдат изчислително скъпи, особено при сложни шаблони или големи входни данни. Ето някои техники за оптимизиране на производителността на regex:
- Бъдете конкретни: Избягвайте използването на прекалено общи шаблони, които могат да съвпаднат с повече от предвиденото.
- Използвайте котви: Закответе regex-а в началото или в края на низа, когато е възможно (
^,$). - Избягвайте backtracking: Минимизирайте връщането назад, като използвате „possessive“ квантификатори (напр.
++вместо+) или „atomic“ групи ((?>...)), когато е подходящо. - Компилирайте веднъж: Ако използвате един и същ regex многократно, компилирайте го веднъж и преизползвайте обекта
RegExp. - Използвайте класове символи разумно: Класовете символи (
[]) обикновено са по-бързи от алтернациите (|). - Поддържайте го просто: Избягвайте прекалено сложни regex-и, които са трудни за разбиране и поддръжка. Понякога разделянето на сложна задача на няколко по-прости regex-а или използването на други техники за обработка на низове може да бъде по-ефективно.
Често срещани грешки при Regex
- Забравяне на екранирането на метасимволи: Пропускането на екраниране на специални символи като
.,*,+,?,$,^,(,),[,],{,},|, и\, когато искате да ги съпоставите буквално. - Прекомерна употреба на
.(точка): Точката съвпада с всеки символ (с изключение на нов ред в някои режими), което може да доведе до неочаквани съвпадения, ако не се използва внимателно. Бъдете по-конкретни, когато е възможно, като използвате класове символи или други по-ограничаващи шаблони. - „Алчност“ (Greediness): По подразбиране квантификаторите като
*и+са „алчни“ и ще съвпаднат с възможно най-много символи. Използвайте „мързеливи“ квантификатори (*?,+?), когато трябва да съвпаднете с възможно най-късия низ. - Неправилно използване на котви: Неразбирането на поведението на
^(начало на низ/ред) и$(край на низ/ред) може да доведе до неправилно съвпадение. Не забравяйте да използвате флагаm(multiline), когато работите с многоредови низове и искате^и$да съвпадат с началото и края на всеки ред. - Необработване на крайни случаи: Пропускането на разглеждане на всички възможни сценарии на входни данни и крайни случаи може да доведе до грешки. Тествайте вашите regex-и обстойно с разнообразни входни данни, включително празни низове, невалидни символи и гранични условия.
- Проблеми с производителността: Създаването на прекалено сложни и неефективни regex-и може да причини проблеми с производителността, особено при големи входни данни. Оптимизирайте вашите regex-и, като използвате по-конкретни шаблони, избягвате ненужно връщане назад и компилирате regex-и, които се използват многократно.
- Игнориране на кодировката на символите: Неправилното боравене с кодировките на символите (особено Unicode) може да доведе до неочаквани резултати. Използвайте флага
u, когато работите с Unicode символи, за да осигурите правилно съвпадение.
Заключение
Регулярните изрази са ценен инструмент за съпоставяне на шаблони и обработка на текст в JavaScript. Овладяването на синтаксиса и техниките на regex ви позволява ефективно да решавате широк кръг от проблеми, от валидиране на данни до сложна обработка на текст. Като разбирате концепциите, обсъдени в това ръководство, и практикувате с реални примери, можете да станете proficient в използването на регулярни изрази, за да подобрите уменията си за разработка с JavaScript.
Помнете, че регулярните изрази могат да бъдат сложни и често е полезно да ги тествате обстойно с онлайн regex тестери като regex101.com или regexr.com. Това ви позволява да визуализирате съвпаденията и да отстранявате ефективно всякакви проблеми. Приятно кодиране!