Досліджуйте розширений пошук за шаблоном у 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}). - Якорі: Відповідають позиціям у рядку (наприклад,
^відповідає початку,$— кінцю).
Поширені метасимволи:
.(крапка): Відповідає будь-якому одному символу, крім нового рядка.^(карет): Відповідає початку рядка.$(долар): Відповідає кінцю рядка.*(зірочка): Відповідає нулю або більше входжень попереднього символу або групи.+(плюс): Відповідає одному або більше входжень попереднього символу або групи.?(знак питання): Відповідає нулю або одному входженню попереднього символу або групи. Використовується для необов'язкових символів.[](квадратні дужки): Визначає клас символів, що відповідає будь-якому одному символу в дужках.{}(фігурні дужки): Вказує кількість входжень для збігу.{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: Відповідає будь-якому «слівному» символу (буквено-цифровий символ плюс підкреслення; еквівалентно[a-zA-Z0-9_]).\W: Відповідає будь-якому неслівному символу (еквівалентно[^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".
Якорі:
^: Відповідає початку рядка. Наприклад,^Helloвідповідає рядкам, які *починаються* з "Hello".$: Відповідає кінцю рядка. Наприклад,World$відповідає рядкам, які *закінчуються* на "World".\b: Відповідає межі слова. Це позиція між слівним символом (\w) і не слівним символом (\W) або початком чи кінцем рядка. Наприклад,\bword\bвідповідає цілому слову "word".
Прапорці:
Прапорці regex змінюють поведінку регулярних виразів. Вони додаються в кінці літералу regex або передаються як другий аргумент конструктору RegExp.
g(global): Шукає всі входження шаблону, а не тільки перше.i(ignore case): Виконує пошук без урахування регістру.m(multiline): Вмикає багаторядковий режим, де^і$відповідають початку і кінцю кожного рядка (розділеного\n).s(dotAll): Дозволяє крапці (.) відповідати також символам нового рядка.u(unicode): Вмикає повну підтримку Unicode.y(sticky): Шукає збіг тільки з індексу, вказаного властивістюlastIndexрегулярного виразу.
Методи Regex у JavaScript
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
Захоплюючі групи:
Круглі дужки () використовуються для створення захоплюючих груп у регулярних виразах. Захоплені групи дозволяють вилучати конкретні частини знайденого тексту. Методи 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 (Третя захоплена група - день)
Іменовані захоплюючі групи:
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
Незахоплюючі групи:
Якщо вам потрібно згрупувати частини 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):
Перевірки — це твердження нульової ширини, які відповідають позиції в рядку на основі шаблону, що передує (lookbehind) або слідує (lookahead) цій позиції, не включаючи сам шаблон перевірки у збіг.
- Позитивна випереджальна перевірка:
(?=...)Збігається, якщо шаблон всередині перевірки *слідує* за поточною позицією. - Негативна випереджальна перевірка:
(?!...)Збігається, якщо шаблон всередині перевірки *не слідує* за поточною позицією. - Позитивна оглядова перевірка:
(?<=...)Збігається, якщо шаблон всередині перевірки *передує* поточній позиції. - Негативна оглядова перевірка:
(? Збігається, якщо шаблон всередині перевірки *не передує* поточній позиції.
Приклад:
// Позитивна випереджальна перевірка: Отримати ціну, тільки якщо за нею слідує USD
const regex = /\d+(?= USD)/;
const text = "The price is 100 USD";
const match = text.match(regex);
console.log(match); // Вивід: ["100"]
// Негативна випереджальна перевірка: Отримати слово, тільки якщо за ним не слідує число
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' ]
// Позитивна оглядова перевірка: Отримати значення, тільки якщо йому передує $
const regex4 = /(?<=\$)\d+/;
const text4 = "The price is $200";
const match4 = text4.match(regex4);
console.log(match4); // Вивід: ["200"]
// Негативна оглядова перевірка: Отримати слово, тільки якщо йому не передує слово 'not'
const regex5 = /(?
Зворотні посилання:
Зворотні посилання дозволяють посилатися на раніше захоплені групи в межах того самого регулярного виразу. Вони використовують синтаксис \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 для валідації email є надзвичайно складним, ось спрощений приклад:
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): Мінімізуйте зворотне відстеження, використовуючи посісивні квантифікатори (наприклад,
++замість+) або атомарні групи ((?>...)), коли це доречно. - Компілюйте один раз: Якщо ви використовуєте один і той самий regex кілька разів, скомпілюйте його один раз і повторно використовуйте об'єкт
RegExp. - Використовуйте класи символів розумно: Класи символів (
[]) зазвичай швидші, ніж чергування (|). - Зберігайте простоту: Уникайте надто складних regex, які важко зрозуміти та підтримувати. Іноді розбиття складного завдання на кілька простіших regex або використання інших технік маніпулювання рядками може бути ефективнішим.
Поширені помилки при роботі з Regex
- Забування екранувати метасимволи: Невміння екранувати спеціальні символи, такі як
.,*,+,?,$,^,(,),[,],{,},|та\, коли ви хочете знайти їх буквально. - Надмірне використання
.(крапки): Крапка відповідає будь-якому символу (крім нового рядка в деяких режимах), що може призвести до несподіваних збігів, якщо використовувати її необережно. Будьте більш конкретними, коли це можливо, використовуючи класи символів або інші більш обмежувальні шаблони. - Жадібність: За замовчуванням квантифікатори, такі як
*і+, є жадібними і будуть відповідати якомога більшій частині рядка. Використовуйте ліниві квантифікатори (*?,+?), коли вам потрібно знайти найкоротший можливий рядок. - Неправильне використання якорів: Неправильне розуміння поведінки
^(початок рядка/лінії) і$(кінець рядка/лінії) може призвести до неправильних збігів. Пам'ятайте про використання прапорцяm(multiline) при роботі з багаторядковими рядками, якщо ви хочете, щоб^і$відповідали початку і кінцю кожного рядка. - Нехтування крайніми випадками: Неврахування всіх можливих сценаріїв вводу та крайніх випадків може призвести до помилок. Ретельно тестуйте свої regex з різноманітними вхідними даними, включаючи порожні рядки, недійсні символи та граничні умови.
- Проблеми з продуктивністю: Створення надто складних і неефективних regex може спричинити проблеми з продуктивністю, особливо з великими обсягами вхідних даних. Оптимізуйте свої regex, використовуючи більш конкретні шаблони, уникаючи непотрібного зворотного відстеження та компілюючи regex, які використовуються неодноразово.
- Ігнорування кодування символів: Неправильна обробка кодувань символів (особливо Unicode) може призвести до несподіваних результатів. Використовуйте прапорець
uпри роботі з символами Unicode, щоб забезпечити правильне зіставлення.
Висновок
Регулярні вирази є цінним інструментом для пошуку за шаблоном та маніпулювання текстом у JavaScript. Опанування синтаксису та технік regex дозволяє ефективно вирішувати широкий спектр завдань, від валідації даних до складної обробки тексту. Розуміючи концепції, обговорені в цьому посібнику, та практикуючись на реальних прикладах, ви можете стати досвідченим у використанні регулярних виразів для покращення своїх навичок розробки на JavaScript.
Пам'ятайте, що регулярні вирази можуть бути складними, і часто буває корисно ретельно тестувати їх за допомогою онлайн-тестерів, таких як regex101.com або regexr.com. Це дозволяє візуалізувати збіги та ефективно налагоджувати будь-які проблеми. Вдалого кодування!