Методы RegExp и String

Есть два набора методов для работы с регулярными выражениями.

  1. Объекты встроенного класса JavaScript RegExp, предоставляющего множество методов;
  2. Методы обычных строк.

Сначала мы рассмотрим методы, а потом – их применение на практических примерах.

str.search(reg)

Данный метод возвращает позицию первого совпадения или -1 , если ничего не найдено.

let str = "A drop of ink may make a million think";

alert( str.search( /a/i ) ); // 0 (первая позиция)

Важная информация: search всегда ищет первое совпадение.

Невозможно найти следующую позицию, используя search. Но есть другие методы, которые это умеют.

str.match(reg), без флага “g”

Поведение метода str.match меняется в зависимости от флага g. Сначала рассмотрим случай без него. Тогда str.match(reg) ищет только первое совпадение. Результат — массив с найденным совпадением и дополнительными свойствами:

  • index – позиция совпадения в строке;
  • input – рассматриваемая строка.

JavaScript RegExp пример:

let str = "Fame is the thirst of youth";

let result = str.match( /fame/i );

alert( result[0] );    // Fame ( совпадение)
alert( result.index ); // 0 (на позиции 0)
alert( result.input ); // "Fame is the thirst of youth" (строка)

В массиве может быть больше одного элемента.

Если часть шаблона выделена скобками (…), то она становится отдельным элементом массива.

Например:

let str = "JavaScript is a programming language";

let result = str.match( /JAVA(SCRIPT)/i );

alert( result[0] ); // JavaScript (все совпадение)
alert( result[1] ); // script (часть совпадения, которая отвечает скобкам)
alert( result.index ); // 0
alert( result.input ); // JavaScript is a programming language

Из-за флага i поиск не зависит от регистра, и находит JavaScript. Часть совпадения, которая отвечает строке SCRIPT, становится отдельным элементом.

str.match(reg) с флагом “g”

Когда есть флаг «g«, str.match возвращает массив всех совпадений. В этом массиве нет дополнительных свойств, а скобки не создают элементы.

Пример использования JavaScript RegExp match:

let str = "HO-Ho-ho!";

let result = str.match( /ho/ig );

alert( result ); // HO, Ho, ho (все совпадения, независимо от регистра)

Со скобками ничего не меняется, смотрите:

let str = "HO-Ho-ho!";

let result = str.match( /h(o)/ig );

alert( result ); // HO, Ho, ho

Итак, с флагом g результат – простой массив совпадений без дополнительных свойств.

Если нужно получить информацию о позициях совпадений и использовать скобки, то следует применить метод RegExp#exec, который мы рассмотрим ниже.

Если совпадений нет, вызов match вернет null.

Если не было совпадений, то результат — не пустой массив, аnull.

Держите это в голове, чтобы избежать подводных камней вроде этого:

let str = "Hey-hey-hey!";

alert( str.match(/ho/gi).length ); // ошибка! У null нет длины

str.split(regexp|substr, limit)

Разбивает строку, используя регулярное выражение (или подстроку) с помощью разделителя.

Мы уже использовали метод Java Script RegExp split со строками, например, так:

alert('12-34-56'.split('-')) // [12, 34, 56]

Но мы также можем передать регулярное выражение:

alert('12-34-56'.split(/-/)) // [12, 34, 56]

str.replace(str|reg, str|func)

Швейцарский нож для поиска и замены в строках. Самое простое применение – поиск и замена подстроки, например:

// заменить тире на двоеточие
alert('12-34-56'.replace("-", ":")) // 12:34-56

Когда первый аргумент replace — это строка, ищет только первое совпадение.

Чтобы найти все тире, нужно использовать не строку «-«, а регулярное выражение /-/g, с обязательным флагом g:

// заменить все тире на двоеточия
alert( '12-34-56'.replace( /-/g, ":" ) )  // 12:34:56

Второй аргумент — это строка-заменитель.

В JavaScript RegExp примерах можно использовать специальные символы:

СимволВставляет
$$«$»
$&все совпадение
$`Часть строки перед совпадением
$’Часть строки после совпадения
$nесли n – число из одной или двух цифр. Это обозначает содержимое n-й скобки, слева направо

Используем $&, чтобы заменить все вхождения «Джон»»John» на «г-н Джон» «Mr.John»:

let str = "John Doe, John Smith and John Bull.";

// для каждого John — заменить на Mr. А затем John
alert(str.replace(/John/g, 'Mr.$&'));
// "Mr.John Doe, Mr.John Smith and Mr.John Bull.";

Скобки часто используются вместе с $1, $2, например:

let str = "John Smith";

alert(str.replace(/(John) (Smith)/, '$2, $1')) // Smith, John

Для ситуаций, которые требуют «умных» замен, второй аргумент может быть функцией. Она будет вызываться для каждого совпадения, а ее результат будет вставлен как замена.

Например:

let i = 0;

// заменить каждое "ho" на результат функции
alert("HO-Ho-ho".replace(/ho/gi, function() {
  return ++i;
})); // 1-2-3

В примере, приведенном выше, функция каждый раз возвращает следующее число, но обычно результат основан на совпадении.

Функция вызывается с аргументами func(str, p1, p2, …, pn, offset, s):

  1. str – совпадение;
  2. p1, p2, …, pn – содержимое скобок (если они есть);
  3. offset – позиция совпадения;
  4. s – исходная строка.

Если в JavaScript RegExp нет скобок, то у функции всегда есть три аргумента: func(str, offset, s). Используем ее, чтобы показать все совпадения:

// показать и заменить все совпадения
function replacer(str, offset, s) {
  alert(`Найдено ${str} на позиции ${offset} в строке ${s}`);
  return str.toLowerCase();
}

let result = "HO-Ho-ho".replace(/ho/gi, replacer);
alert( 'Результат: ' + result ); // Результат: ho-ho-ho

// Показать каждое совпадение:
// Найдено HO на позиции 0 в строке HO-Ho-ho
// Найдено Ho на позиции 3 в строке HO-Ho-ho
// Найдено ho на позиции 6 в строке HO-Ho-ho

В примере, приведенном ниже, есть две скобки, так что replacerзаменитель») вызывается с пятью аргументами: str — это полное совпадение, скобки, а затем offset (смещение) и s:

function replacer(str, name, surname, offset, s) {
  // имя - это первые круглые скобки, фамилия – вторые 
  return surname + ", " + name;
}

let str = "John Smith";

alert(str.replace(/(John) (Smith)/, replacer)) // Smith, John

Функция получает всю информацию о совпадениях, имеет доступ к внешним переменным и может делать все.

regexp.test(str)

Перейдем к методам класса RegExp, которые вызываются для самих регулярных выражений.

Метод JavaScript RegExp test ищет любое совпадение и возвращает результат true/false. Так что это в принципе то же самое, что и str.search(reg) != -1, например:

let str = "I love JavaScript";

// эти две проверки делают одно и то же
alert( /love/i.test(str) ); // true
alert( str.search(/love/i) != -1 ); // true

Пример с отрицательным ответом:

str = "Bla-bla-bla";

alert( /love/i.test(str) ); // false
alert( str.search(/love/i) != -1 ); // false

regexp.exec(str)

Мы уже рассматривали эти методы:

  • search – ищет позицию совпадения;
  • match – если нет флага g, возвращает первое совпадение со скобками;
  • match – если есть флаг g – возвращает все совпадения без отделяющих скобок.

Метод regexp.exec труднее использовать, но он позволяет искать все совпадения без скобок и позиций. Ведет себя по-разному, в зависимости от наличия флага g в регулярном выражении.

  • Если нет флага g, то regexp.exec(str) возвращает первое совпадение, так же как str.match(reg);
  • Если есть флаг g, то regexp.exec(str) возвращает первое совпадение и запоминает позицию за ним в свойстве regexp.lastIndex.

Следующий вызов начнет искать с позиции regexp.lastIndex и вернет следующее совпадение. Если совпадений больше нет, то regexp.exec возвращает null, а regexp.lastIndex устанавливается в 0.

Как видим, метод JavaScript RegExp exec не дает ничего нового, если мы используем его без флага g, так как str.match делает то же самое. Но флаг g позволяет получить все совпадения с их позициями и группами скобок.

Вот пример того, как последовательные вызовы regexp.exec возвращают совпадения одно за другим:

let str = "A lot about JavaScript at https://javascript.info";

let regexp = /JAVA(SCRIPT)/ig;

// Ищем первое совпадение
let matchOne = regexp.exec(str);
alert( matchOne[0] ); // JavaScript
alert( matchOne[1] ); // script
alert( matchOne.index ); // 12 (позиция совпадения)
alert( matchOne.input ); // то же, что и str

alert( regexp.lastIndex ); // 22 (позиция после совпадения)

// Ищем второе совпадение
let matchTwo = regexp.exec(str); // продолжим поиск с индекса regexp.lastIndex
alert( matchTwo[0] ); // javascript
alert( matchTwo[1] ); // script
alert( matchTwo.index ); // 34 (позиция совпадения)
alert( matchTwo.input ); // то же, что и str

alert( regexp.lastIndex ); // 44 (позиция после совпадения)

// Ищем третье совпадение
let matchThree = regexp.exec(str); // продолжим поиск с regexp.lastIndex
alert( matchThree ); // null (без совпадений)

alert( regexp.lastIndex ); // 0 (сбрасывание)

Как видим, каждый вызов JavaScript RegExp exec возвращает совпадение в «полном формате»: как массив со скобками, свойствами index и input.

Основное применение regexp.exec – находить все совпадения в цикле:

let str = 'A lot about JavaScript at https://javascript.info';

let regexp = /javascript/ig;

let result;

while (result = regexp.exec(str)) {
  alert( `Найдено ${result[0]} на позиции ${result.index}` );
}

Цикл продолжается, пока regexp.exec не вернет null, что значит «совпадений больше нет».

Поиск от заданной позиции

Можно заставить regexp.exec начать поиск с данной позиции, установив вручную lastIndex:

let str = 'A lot about JavaScript at https://javascript.info';

let regexp = /javascript/ig;
regexp.lastIndex = 30;

alert( regexp.exec(str).index ); // 34, поиск начинается с 30-й позиции

Флаг «y»

Флаг y значит, что поиск должен найти совпадение на позиции, указанной в свойстве regexp.lastIndex, и только там.

Другими словами, обычно поиск идет по всей строке: /javascript/ ищет подстроку “javascript”. Но когда JavaScript RegExp содержит флаг y, оно ищет только совпадение на позиции, указанной в regexp.lastIndex(по умолчанию — 0).

Например:

let str = "I love JavaScript!";

let reg = /javascript/iy;

alert( reg.lastIndex ); // 0 (по умолчанию)
alert( str.match(reg) ); // null, не найдено на позиции 0

reg.lastIndex = 7;
alert( str.match(reg) ); // JavaScript (это слово начинается на позиции 7)

// для любого другого индекса reg.lastIndex результат - null

Регулярное выражение /javascript/iy может быть найдено, только если установить reg.lastIndex=7, так как из-за флага y программа пытается найти его только в одном месте внутри строки — на позиции reg.lastIndex.

Так в чем же смысл? В производительности.

Флаг y отлично работает для парсеров – программ, которым нужно «читать» текст и строить в памяти синтаксическую структуру или производить на ее основании действия. Для этого мы двигаемся по тексту и применяем регулярные выражения, чтобы увидеть, что находится дальше: строка, число или что-то еще.

Флаг y позволяет применять регулярное выражение (или несколько одно за другим) именно на заданной позиции. И когда мы поймем, что там, то сможем двигаться дальше, шаг за шагом исследуя текст.

Без флага y регулярное выражение всегда ищет до конца текста, что занимает время, особенно если текст большой. Тогда наш парсер будет медленным. Флаг y — это то, что нужно в подобном случае.

Выводы, рецепты

Будет намного проще понять методы JavaScript RegExp, если мы разделим их по использованию на практике.

Чтобы найти только первое совпадение:

  • Найти позицию первого совпадения – str.search(reg);
  • Найти полное совпадение – str.match(reg);
  • Проверить, есть ли совпадение – regexp.test(str);
  • Найти совпадение с заданной позиции – regexp.exec(str), установите regexp.lastIndex в номер позиции.

Чтобы найти все совпадения:

  • Массив совпадений – str.match(reg), регулярное выражение с флагом g;
  • Получить все совпадения с полной информацией о каждом из них – regexp.exec(str) с флагом g в цикле.

Чтобы найти и заменить:

  • Заменить одну строку на другую или результат работы функции – str.replace(reg, str|func).

Чтобы разделить строку:

  • str.split(str|reg).

Кроме этого мы изучили два флага JavaScript RegExp:

  • Флаг g — чтобы найти все совпадения (глобальный поиск);
  • Флаг y — чтобы искать на точно заданной позиции внутри текста.

Теперь мы знаем методы и можем использовать регулярные выражения.

Пожалуйста, оставьте ваши комментарии по текущей теме материала. За комментарии, лайки, подписки, отклики, дизлайки низкий вам поклон!

Пожалуйста, оставляйте ваши комментарии по текущей теме статьи. За комментарии, отклики, лайки, дизлайки, подписки низкий вам поклон!

КККонстантин Кондрусинавтор статьи «Methods of RegExp and String»