Методы 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” был подготовлен дружной командой проекта Сайтостроение от А до Я.