В цій статті ми детально розглянемо різні методи для роботи з регулярними виразами.
str.match(regexp)
Метод str.match(regexp)
знаходить збіги для regexp
в рядку str
.
Він має 3 режими:
-
Якщо
regexp
не має прапоруg
, тоді він повертає перший збіг у вигляді масиву з групами захоплення та властивостямиindex
(позиція збігу),input
(введений рядок, дорівнюєstr
):let str = "Я люблю JavaScript"; let result = str.match(/Java(Script)/); alert( result[0] ); // JavaScript (повний збіг) alert( result[1] ); // Script (перша група захоплення) alert( result.length ); // 2 // Додаткова інформація: alert( result.index ); // 7 (позиція збігу) alert( result.input ); // Я люблю JavaScript (вихідний рядок)
-
Якщо
regexp
має прапорg
, тоді він повертає масив всіх збігів у вигляді рядків, без груп захоплення та інших деталей.let str = "Я люблю JavaScript"; let result = str.match(/Java(Script)/g); alert( result[0] ); // JavaScript alert( result.length ); // 1
-
Якщо збігів нема, повертається
null
, незалежно від наявності прапоруg
.Це важливий нюанс. Якщо збігів нема, ми отримаємо не порожній масив, а
null
. Легко помилитись, забувши про це:let str = "Я люблю JavaScript"; let result = str.match(/HTML/); alert(result); // null alert(result.length); // Error: Cannot read property 'length' of null
Якщо ми хочемо, аби результат був масивом, ми можемо написати:
let result = str.match(regexp) || [];
str.matchAll(regexp)
Метод str.matchAll(regexp)
– це “новіший, покращений” варіант str.match
.
В основному, його використовують для пошуку всіх збігів з усіма групами.
Існує 3 відмінності від match
:
- Він повертає ітерований об’єкт із збігами замість масиву. Ми можемо отримати з нього звичайний масив за допомогою
Array.from
. - Кожен збіг повертається у вигляді масиву з групами захоплення (той самий формат, що й
str.match
без прапоруg
). - Якщо результатів нема, метод повертає порожній ітерований об’єкт замість
null
.
Приклад використання:
let str = '<h1>Вітаю, світе!</h1>';
let regexp = /<(.*?)>/g;
let matchAll = str.matchAll(regexp);
alert(matchAll); // [object RegExp String Iterator], не масив, але ітерований
matchAll = Array.from(matchAll); // тепер масив
let firstMatch = matchAll[0];
alert( firstMatch[0] ); // <h1>
alert( firstMatch[1] ); // h1
alert( firstMatch.index ); // 0
alert( firstMatch.input ); // <h1>Вітаю, світе!</h1>
Якщо ми використаємо for..of
для циклічного проходження збігами matchAll
, тоді ми більше не потребуємо Array.from
.
str.split(regexp|substr, limit)
Ділить рядок, використовуючи регулярний вираз (або підрядок) в якості роздільника.
Можна використовувати split
з рядками, як-то:
alert('12-34-56'.split('-')) // array of ['12', '34', '56']
Але ми так само можемо ділити за допомогою регулярного виразу:
alert('12, 34, 56'.split(/,\s*/)) // array of ['12', '34', '56']
str.search(regexp)
Метод str.search(regexp)
повертає позицію першого збігу або -1
, якщо нічого не знайдено:
let str = "Чорнил краплина – мільйонів думок причина";
alert( str.search( /ink/i ) ); // 8 (позиція першого збігу)
Важливе обмеження: search
знаходить лише перший збіг.
Якщо нам потрібні позиції подальших збігів, нам слід пошукати інші варіанти, як-то повний пошук за допомогою str.matchAll(regexp)
.
str.replace(str|regexp, str|func)
Це загальний метод для пошуку та заміни, один з найбільш корисних. «Швейцарський ніж» подібних операцій.
Ми можемо користуватись ним без регулярних виразів, шукаючи та замінюючи підрядок:
// заміна дефіс на двокрапку
alert('12-34-56'.replace("-", ":")) // 12:34-56
Але тут є своє підводне каміння.
Коли перший аргумент replace
є рядком, він замінює лише перший збіг.
Ви можете побачити це в попередньому прикладі: лише перше "-"
замінюється на ":"
.
Аби знайти всі дефіси, нам потрібно використати не рядок "-"
, а регулярний вираз /-/g
, з обов’язковим прапором g
:
// заміна всіх дефісів на двокрапку
alert( '12-34-56'.replace( /-/g, ":" ) ) // 12:34:56
Другий аргумент є рядком для заміни. В ньому можуть міститись спеціальні символи:
Символи | Роль в рядку для заміни |
---|---|
$& |
вставляє повний збіг |
$` |
вставляє частину рядку, що йшла перед збігом |
$' |
вставляє частину рядку, що йшла після збігу |
$n |
якщоn є одно-двоцифровим числом, вставляє вміст n-ної групи захоплення, детальніше тут Групи захоплення |
$<name> |
вставляє вміст іменованих дужок name , детальніше тут Групи захоплення |
$$ |
вставляє символ $ |
Для прикладу:
let str = "Іван Сірко";
// переставляє місцями ім’я та прізвище
alert(str.replace(/(іван) (сірко)/i, '$2, $1')) // Сірко, Іван
Для ситуацій, які потребують “розумної” перестановки, другий аргумент може бути функцією.
Вона викликатиметься для кожного збігу, а повернене значення вставлятиметься як заміна.
Функція містить аргументи func(match, p1, p2, ..., pn, offset, input, groups)
:
match
– збіг,p1, p2, ..., pn
– вміст груп захоплення (якщо є в наявності),offset
– позиція збігу,input
– вихідний рядок,groups
– об’єкт з іменованими групами.
Якщо в регулярному виразі нема дужок, тоді є лише 3 аргументи: func(str, offset, input)
.
Наприклад, приведемо всі збіги до верхнього регістру:
let str = "html та css";
let result = str.replace(/html|css/gi, str => str.toUpperCase());
alert(result); // HTML та CSS
Замінимо кожен збіг на його позицію в рядку:
alert("Хо-хо-хо".replace(/хо/gi, (match, offset) => offset)); // 0-3-6
В прикладі нижче, наявні дві дужки, тож функція заміни матиме 5 аргументів: перший є повним збігом, далі 2 дужки, після цього (не використані в прикладі) позиція збігу та вихідний рядок:
let str = "Іван Сірко";
let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);
alert(result); // Сірко, Іван
Якщо груп багато, зручно використовувати залишкові параметри для доступу до них:
let str = "Іван Сірко";
let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);
alert(result); // Сірко, Іван
В іншому випадку, якщо ми використовуємо іменовані групи, тоді об’єкт groups
завжди йде останнім після них, тож ми можемо отримати його наступним чином:
let str = "Іван Сірко";
let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
let groups = match.pop();
return `${groups.surname}, ${groups.name}`;
});
alert(result); // Сірко, Іван
Використання функції максимально розкриває можливості заміни, тому що ми отримуємо всю інформацію про збіг, доступ до зовнішніх змінних та можемо робити все, що потрібно.
str.replaceAll(str|regexp, str|func)
Цей метод, по суті, такий самий, що й str.replace
, з двома значними відмінностями:
- Якщо перший аргумент є рядком, він замінює всі входження в рядку, тоді як
replace
замінює лише перше входження. - Якщо перший аргумент є регулярним виразом без прапору
g
, виникне помилка. З прапоромg
, метод працюватиме аналогічно доreplace
.
Основний випадок використання replaceAll
– заміна всіх входжень збігу в рядку.
Наприклад:
// замінює всі дефіси на двокрапку
alert('12-34-56'.replaceAll("-", ":")) // 12:34:56
regexp.exec(str)
Метод regexp.exec(str)
повертає збіг для regexp
в рядку str
. На відміну від попередніх методів, він застосовується до регулярного виразу, а не рядку.
Його поведінка залежить від наявності в регулярному виразі прапору g
.
Якщо нема прапору g
, тоді regexp.exec(str)
повертає перший збіг у вигляді str.match(regexp)
. Ця поведінка не додає нічого нового.
Але за наявності g
:
- Виклик
regexp.exec(str)
повертає перший збіг та зберігає позицію після нього всередині властивостіregexp.lastIndex
. - Наступний виклик починає пошук з позиції
regexp.lastIndex
, повертає наступний збіг та зберігає позицію після вregexp.lastIndex
. - …І так далі.
- Якщо збігів нема,
regexp.exec
повертаєnull
та скидаєregexp.lastIndex
до0
.
Тож, повторювані виклики один за одним повертають всі збіги, використовуючи властивість regexp.lastIndex
для відслідковування поточної позиції пошуку.
В минулому, коли метод str.matchAll
ще не був доданий в JavaScript, виклики regexp.exec
використовувались в циклі для отримання всіх збігів, разом з групами:
let str = 'Детальніше про JavaScript тут https://javascript.info';
let regexp = /javascript/ig;
let result;
while (result = regexp.exec(str)) {
alert( `${result[0]} знайдений на позиції ${result.index}` );
// JavaScript знайдений на позиції 11
// javascript знайдений на позиції 33
}
Нині це також працює, хоча для новіших браузерів str.matchAll
, зазвичай, зручніший.
Можна використовувати regexp.exec
для пошуку із зазначеної позиції, вручну змінивши lastIndex
.
Для прикладу:
let str = 'Вітаю, світе!';
let regexp = /\w+/g; // без прапору "g", властивість lastIndex ігнорується
regexp.lastIndex = 5; // пошук з п’ятої позиції (з коми)
alert( regexp.exec(str) ); // світе
Якщо регулярний вираз має прапор y
, тоді пошук проводитиметься абсолютно чітко з позиції regexp.lastIndex
.
У прикладі зверху, замінимо прапор g
на y
. Збігів не буде, бо на 5 позиції нема слова:
let str = 'Вітаю, світе!';
let regexp = /\w+/y;
regexp.lastIndex = 5; // пошук саме з 5 позиції
alert( regexp.exec(str) ); // null
Це зручно для ситуацій, коли нам потрібно “зчитати” щось з рядку регулярним виразом з чітко визначеної позиції, не шукаючи далі.
regexp.test(str)
Метод regexp.test(str)
шукає збіг та повертає true/false
в залежності від його наявності.
Для прикладу:
let str = "Я люблю JavaScript";
// ці два тести роблять одне й те саме
alert( /люблю/i.test(str) ); // true
alert( str.search(/люблю/i) != -1 ); // true
Приклад з негативним результатом:
let str = "Бла-бла-бла";
alert( /люблю/i.test(str) ); // false
alert( str.search(/люблю/i) != -1 ); // false
Якщо регулярний вираз має прапор g
, тоді regexp.test
проводить пошук, починаючи з regexp.lastIndex
та оновлює цю властивість, аналогічно до regexp.exec
.
Тож ми можемо використовувати його для пошуку з визначеної позиції:
let regexp = /люблю/gi;
let str = "Я люблю JavaScript";
// починаємо пошук з позиції 10:
regexp.lastIndex = 10;
alert( regexp.test(str) ); // false (збігу нема)
Якщо ми застосуємо один і той самий глобальний регулярний вираз до різних вхідних даних, можемо отримати неправильні результати, бо виклик regexp.test
посуває властивість regexp.lastIndex
, тож пошук в іншому рядку може початись з позиції, відмінної від нуля.
Для прикладу, ми викликаємо regexp.test
двічі для одного тексту, та другий раз повертається помилковий результат:
let regexp = /javascript/g; // (новостворений регулярний вираз: regexp.lastIndex=0)
alert( regexp.test("javascript") ); // true (наразі regexp.lastIndex=10)
alert( regexp.test("javascript") ); // false
Проблема саме в regexp.lastIndex
, що не є нульовим під час виконання другого тесту.
Оминути це можна, встановлюючи regexp.lastIndex = 0
перед кожним пошуком. Іншим рішенням є використання методів рядків str.match/search/...
замість методів регулярних виразів, так як вони не використовують lastIndex
.
Коментарі
<code>
, для кількох рядків – обгорніть їх тегом<pre>
, для понад 10 рядків – використовуйте пісочницю (plnkr, jsbin, codepen…)