В этой главе мы рассмотрим все детали методов для работы с регулярными выражениями.
str.match(regexp)
Метод str.match(regexp)
ищет совпадения с regexp
в строке str
.
У него есть три режима работы:
-
Если у регулярного выражения нет флага
g
, то он возвращает первое совпадение в виде массива со скобочными группами и свойствамиindex
(позиция совпадения),input
(строка поиска, равнаstr
):let str = "I love 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 ); // I love JavaScript (исходная строка)
-
Если у регулярного выражения есть флаг
g
, то он возвращает массив всех совпадений, без скобочных групп и других деталей.let str = "I love JavaScript"; let result = str.match(/Java(Script)/g); alert( result[0] ); // JavaScript alert( result.length ); // 1
-
Если совпадений нет, то, вне зависимости от наличия флага
g
, возвращаетсяnull
.Это очень важный нюанс. При отсутствии совпадений возвращается не пустой массив, а именно
null
. Если об этом забыть, можно легко допустить ошибку, например:let str = "I love JavaScript"; let result = str.match(/HTML/); alert(result); // null alert(result.length); // Ошибка: у null нет свойства length
Если хочется, чтобы результатом всегда был массив, можно написать так:
let result = str.match(regexp) || [];
str.matchAll(regexp)
Метод str.matchAll(regexp)
– «новый, улучшенный» вариант метода str.match
.
Он используется, в первую очередь, для поиска всех совпадений вместе со скобочными группами.
У него 3 отличия от match
:
- Он возвращает не массив, а перебираемый объект с результатами, обычный массив можно сделать при помощи
Array.from
. - Каждое совпадение возвращается в виде массива со скобочными группами (как
str.match
без флагаg
). - Если совпадений нет, то возвращается не
null
, а пустой перебираемый объект.
Пример использования:
let str = '<h1>Hello, world!</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>Hello, world!</h1>
При переборе результатов matchAll
в цикле for..of
вызов Array.from
, разумеется, не нужен.
str.split(regexp|substr, limit)
Разбивает строку в массив по разделителю – регулярному выражению regexp или подстроке substr.
Обычно мы используем метод split
со строками, вот так:
alert('12-34-56'.split('-')) // массив [12, 34, 56]
Но мы можем разделить по регулярному выражению аналогичным образом:
alert('12, 34, 56'.split(/,\s*/)) // массив [12, 34, 56]
str.search(regexp)
Метод str.search(regexp)
возвращает позицию первого совпадения с regexp
в строке str
или -1
, если совпадения нет.
Например:
let str = "Я люблю JavaScript!";
let regexp = /Java.+/;
alert( str.search(regexp) ); // 8
Важное ограничение: str.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 это 1-2 значное число, то вставляет содержимое n-й скобки (см. главу Скобочные группы) |
$<name> |
вставляет содержимое скобки с указанным name (см. главу Скобочные группы) |
$$ |
вставляет "$" |
Например:
let str = "John Smith";
// поменять местами имя и фамилию
alert(str.replace(/(\w+) (\w+)/i, '$2, $1')) // Smith, John
Для ситуаций, которые требуют «умных» замен, вторым аргументом может быть функция.
Она будет вызываться для каждого совпадения, и её результат будет вставлен в качестве замены.
Функция вызывается с аргументами func(match, p1, p2, ..., pn, offset, input, groups)
:
match
– найденное совпадение,p1, p2, ..., pn
– содержимое скобок (см. главу Скобочные группы).offset
– позиция, на которой найдено совпадение,input
– исходная строка,groups
– объект с содержимым именованных скобок (см. главу Скобочные группы).
Если скобок в регулярном выражении нет, то будет только 3 аргумента: func(match, offset, input)
.
Например, переведём выбранные совпадения в верхний регистр:
let str = "html and css";
let result = str.replace(/html|css/gi, str => str.toUpperCase());
alert(result); // HTML and CSS
Заменим каждое совпадение на его позицию в строке:
alert("Хо-Хо-хо".replace(/хо/gi, (match, offset) => offset)); // 0-3-6
В примере ниже две скобки, поэтому функция замены вызывается с 5-ю аргументами: первый – всё совпадение, затем два аргумента содержимое скобок, затем (в примере не используются) индекс совпадения и исходная строка:
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (match, name, surname) => `${surname}, ${name}`);
alert(result); // Smith, John
Если в регулярном выражении много скобочных групп, то бывает удобно использовать остаточные аргументы для обращения к ним:
let str = "John Smith";
let result = str.replace(/(\w+) (\w+)/, (...match) => `${match[2]}, ${match[1]}`);
alert(result); // Smith, John
Или, если мы используем именованные группы, то объект groups
с ними всегда идёт последним, так что можно получить его так:
let str = "John Smith";
let result = str.replace(/(?<name>\w+) (?<surname>\w+)/, (...match) => {
let groups = match.pop();
return `${groups.surname}, ${groups.name}`;
});
alert(result); // Smith, John
Использование функции даёт нам максимальные возможности по замене, потому что функция получает всю информацию о совпадении, имеет доступ к внешним переменным и может делать всё что угодно.
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 на позиции 9, затем
// Найдено javascript на позиции 31
}
Это работает и сейчас, хотя для современных браузеров str.matchAll
, как правило, удобнее.
Мы можем использовать regexp.exec
для поиска совпадения, начиная с нужной позиции, если вручную поставим lastIndex
.
Например:
let str = 'Hello, world!';
let regexp = /\w+/g; // без флага g свойство lastIndex игнорируется
regexp.lastIndex = 5; // ищем с 5-й позиции (т.е с запятой и далее)
alert( regexp.exec(str) ); // world
Если у регулярного выражения стоит флаг y
, то поиск будет вестись не начиная с позиции regexp.lastIndex
, а только на этой позиции (не далее в тексте).
В примере выше заменим флаг g
на y
. Ничего найдено не будет, поскольку именно на позиции 5
слова нет:
let str = 'Hello, world!';
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 только что создан: 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
.