JS для начинающих. Урок 1.18: Использование регулярных выражений

В прошлом уроке мы рассмотрели синтаксис регулярных выражений, но не обсудили, как именно их можно использовать в JavaScript. В этом уроке мы подробно рассмотрим методы объекта String для поиска и замены по регулярному выражению, а так же обратим внимание на объект RegExp и его свойства и методы.

Метод search объекта String

Это, пожалуй, самый простой и малоинформативный метод объекта String для работы с регулярными выражениями. Он принимает в качестве параметра регулярное выражение или строку, которая будет к нему преобразована и возвращает позицию подстроки, совпавшей с регулярным выражением или -1, если такой подстроки не нашлось.

var site = "true-coder.ru";
 
console.log(site.search(/true/)); // 0
console.log(site.search(/coder/)); // 5
 
console.log(site.search(/something/)); // -1
 
var code = "console.log(255 == 0xff); // true";
 
console.log(code.search("\\d{3}")); // 12
console.log(code.search("0x[\\da-f]+")); // 19

В прошлом уроке мы также упоминали про флаги регулярных выражениях. Продемонстрируем использования флага i, который позволяет игнорировать регистр при поиске подстроки.

var jsIgnoreCasePatter = /javascript/i;
 
console.log("I will code javascript for food"
  .search(jsIgnoreCasePatter)); // 12
 
console.log("I will code JavaScript for food"
  .search(jsIgnoreCasePatter)); // 12

Обратим внимание на флаг m, который включает режим многострочного поиска. Продемонстрируем его использование.

var multilineText = "first line\nsecond line";
 
console.log(multilineText.search(/line$/)); // 18
console.log(multilineText.search(/line$/m)); // 16

Обратите внимание, на то, что символ $ расценивается как конец всего текста, если флаг m опущен, и как конец строки в противном случае.

Флаг g игнорируется в регулярном выражении, которое передаётся в качестве параметра методу search.

Метод split объекта String

Этот метод служит для разбиения строки на части. В качестве параметра он принимает разделитель, по которому будет производиться разбиение. Разделитель может быть, как и просто строкой, так и регулярным выражением. Для примера, давайте разрежем строку на части по запятым.

var numbers = "1,two,three,4,five";
 
numbers = numbers.split(',');
console.log(numbers); // ["1", "two", "three", "4", "five"]

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

var numbers = "1, two, three, 4 , five";
 
numbers = numbers.split(/\s*,\s*/);
console.log(numbers); // ["1", "two", "three", "4", "five"]

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

var numbers = "1, two, three, 4 , five";
 
numbers = numbers.split(/\s*,\s*/, 2);
console.log(numbers); // ["1", "two"]

Meтод match объекта String

Этот метод более информативен, чем метод search. В качестве параметра он так же принимает регулярное выражение, или строку, которая к нему преобразуется. Результатом выполнения этого метода служит массив, содержащий результаты поиска. Если в регулярном выражении отсутствует флаг g, то поиск будет вестись только до первого совпадения. Массив результатов в качестве первого элемента будет содержать строку совпавшею со всем регулярным выражением, а в качестве последующих – подстроки, совпадающие с шаблонами в скобках. Так же массив результатов имеет свойство index, содержащие позицию первого совпавшего символа, и свойство input, которое ссылается на строку, в которой выполнялся поиск. Чтобы продемонстрировать это, давайте напишем функцию, которая выводит информацию о математическом выражении, и воспользуемся ей пару раз.

var expressionPattern = /(\d+)\s*([\+\*\-\/])\s*(\d+)/,
  additionExpression = "addition: 2 + 2",
  subtractionExpression = "subsraction: 50 - 4";
 
function showExpressionInfo(expression) {
  var matches = expression.match(expressionPattern);
  /*
   * Строка, совпавшая с регулярным выражением.
   * В нашем случае математическое выражение.
   */
  console.log("Expression: " + matches[0]);
  // Первый аргумент выражения (первые скобки (\d+))
  console.log("a = " + matches[1]);
  // Второй аргумент выражения (третьи скобки (\d+))
  console.log("b = " + matches[3]);
  // Знак математической операции (вторые скобки ([\+\*\-\/]))
  console.log("sign: " + matches[2]);
 
  console.log("index: " + matches.index);
  console.log("input: " + matches.input);
}
 
/*
 * Expression: 2 + 2
 * a = 2
 * b = 2
 * sign: +
 * index: 10
 * input: addition: 2 + 2
 */
 
showExpressionInfo(additionExpression);
 
/*
 * Expression: 50 - 4
 * a = 50
 * b = 4
 * sign: -
 * index: 13
 * input: subsraction: 50 - 4 
 */
 
showExpressionInfo(subtractionExpression);

Если в регулярном выражении присутствует флаг g, то поиск осуществляется по всей строке и массив результатов содержит все подстроки, совпавшие с регулярным выражением, за исключением подстрок совпадающих с регулярными выражениями в скобках. Свойства index и input в этом случае у массива результатов отсутствуют.

var expressionPattern = /(\d+)\s*([\+\*\-\/])\s*(\d+)/g,
  math = "5 + 5 = ?; 10 - 3 = ?; 5 * 6 = ?; 8 / 2 = ?",
  matches = math.match(expressionPattern);
 
console.log(matches); // ["5 + 5", "10 - 3", "5 * 6", "8 / 2"]

Если подстрок удовлетворяющим регулярному выражению в строке не найдено, то метод возвращает null.

var str = "violets are blue";
 
console.log(str.match(/\d/)); // null

Метод replace объекта String

Этот метод служит для поиска с заменой. В качестве первого аргумента он принимает регулярное выражение, а в качестве второго строку замены. Если у регулярного выражения установлен флаг g, то метод заменяет все найденные совпадения, а в противном случае только первое.

var str = "Baby, I know js. Js is very fun!";
 
// Baby, I know JavaScript. Js is very fun! 
console.log(str.replace(/js/, "JavaScript"));
 
// Baby, I know JavaScript. JavaScript is very fun! 
console.log(str.replace(/js/ig, "JavaScript"));

Так же обратите внимание на присутствие во втором случаем флага i. Без него второе упоминание js не было бы найдено в строке.

Если в строке замены присутствует знак $ cо следующим за ним номером подвыражения в скобках, то метод replace заменяет эти символы на совпавшую с подвыражением строку. В качестве примера давайте поменяем местами операнды в строке с математическими выражениями.

var expressionPatter = /(\d+)(\s*[\+\*]\s*)(\d+)/g,
  expressions = "2 + 4, 3+5, 7  * 8";
 
// 4 + 2, 5+3, 8  * 7 
console.log(expressions.replace(expressionPatter, "$3$2$1"));

Самый интересный случай использования этого метода, это когда в качестве второго аргумента передаётся функция замены. Эта функция в качестве первого параметра принимает совпавшую с регулярным выражением подстроку, затем следуют аргументы, которые содержат строки, совпавшие с подвыражениями в скобках, а в качестве последних двух аргументов функция принимает позицию первого совпавшего символа и строку, в которой производится замена.

var expressionsPattern = /(\d+)\s*([\+\-])\s*(\d+)/g,
  expressions = "2 + 4, 5-3, 7  + 8",
  calcAndReplace = function (str, a, operation, b, pos, s) {
 
    /*
     * str - совпавшая подстрока. 
     * В нашем случае это "2 + 4", "5-3" и "7  + 8"
     *
     * pos - позиция первого символа совпавшей подстроки. 
     * В нашем случае 0, 7 и 12.
     *
     * s - исходная строка, в которой производится замена
     * Не меняется в ходе замены!
     */
 
    var a = parseInt(a),
      b = parseInt(b);
 
    if (operation == '+') {
      return a + b;
    } else if (operation == '-') {
      return a - b;
    }
  },
  results;
 
results = expressions.replace(expressionsPattern, calcAndReplace);
 
console.log(results); // 6, 2, 15

Свойства объекта RegExp

У объекта RegExp есть пять свойств. Три из них содержат логические значения, которые свидетельствуют о наличии, или отсутствии у регулярного выражения флагов. Это свойства: ignoreCase, global и multiline. Имена соответствующих им флагов — это первая буква этих свойств. Давайте напишем код, который это демонстрирует.

var withoutFlags = /regexp/,
  withIFlag = /regexp/i,
  withGFlag = /regexp/g,
  withMFlag = /regexp/m,
  withIGFlag = /regexp/ig,
  withAllFlags = /regexp/igm;
 
function showFlagInfo(regExp) {
  var info = 'i: ' + regExp.ignoreCase +
    '; g: ' + regExp.global +
    '; m: ' + regExp.multiline;
 
  console.log(info);    
}
 
// i: false; g: false; m: false 
showFlagInfo(withoutFlags);
 
// i: true; g: false; m: false 
showFlagInfo(withIFlag);
 
// i: false; g: true; m: false 
showFlagInfo(withGFlag);
 
// i: false; g: false; m: true
showFlagInfo(withMFlag);
 
// i: true; g: true; m: false
showFlagInfo(withIGFlag);
 
// i: true; g: true; m: true
showFlagInfo(withAllFlags);

У объекта RegExp так же есть свойство source, доступное только для чтения, в котором лежит строка с регулярным выражением.

var digits = /\d+/;
 
console.log(digits.source); // \d+
 
digits.source = 'anotherRegExp';
 
console.log(digits.source); // \d+

И последние свойство объекта RegExp это свойство lastIndex, которое содержит индекс последней совпавшей с регулярным выражением подстроки. О нём мы поговорим далее.

Метод exec объекта RegExp

Этот метод аналогичен методу match объекта String, за исключением того, что это метод объекта RegExp, принимающий в качестве параметра строку, а не метод объекта String, принимающий в качестве параметра регулярное выражение. Так же, в отличие от match, этот метод всегда, вне зависимости от того установлен ли флаг g, возвращает массив, нулевым элементом которого является подстрока, совпавшая с регулярным выражением, а последующие элементы этого массива это строки, совпавшие с частями регулярного выражения в скобках. Возвращаемый массив полностью аналогичен тому, что возвращает метод match объекта String. У него так же присутствуют свойства input и index.

var expressionPattern = /(\d+)\s*([\+\*\-\/])\s*(\d+)/,
  additionExpression = "addition: 2 + 2",
  subtractionExpression = "subsraction: 50 - 4";
 
function showExpressionInfo(expression) {
  var matches = expressionPattern.exec(expression)
  /*
   * Строка, совпавшая с регулярным выражением.
   * В нашем случае математическое выражение.
   */
  console.log("Expression: " + matches[0]);
  // Первый аргумент выражения (первые скобки (\d+))
  console.log("a = " + matches[1]);
  // Второй аргумент выражения (третьи скобки (\d+))
  console.log("b = " + matches[3]);
  // Знак математической операции (вторые скобки ([\+\*\-\/]))
  console.log("sign: " + matches[2]);
 
  console.log("index: " + matches.index);
  console.log("input: " + matches.input);
}
 
/*
 * Expression: 2 + 2
 * a = 2
 * b = 2
 * sign: +
 * index: 10
 * input: addition: 2 + 2
 */
 
showExpressionInfo(additionExpression);
 
/*
 * Expression: 50 - 4
 * a = 50
 * b = 4
 * sign: -
 * index: 13
 * input: subsraction: 50 - 4 
 */
showExpressionInfo(subtractionExpression);

Однако наличие флага g в регулярном выражении всё-таки влияет на поведение метода exec. При отсутствии этого флага метод всегда оставляет свойство lastIndex регулярного выражения равным нулю. Если же этот флаг установлен, то в этом свойстве находится позиция последнего совпавшего с регулярным выражением символа. С этой позиции начнётся поиск, когда метод exec будет вновь вызван для этого же регулярного выражения.

var str = "Baby, I know js. Js is very fun!",,
  jsPatternWithGFlag = /js/ig;
 
 // ["js", index: 13, input: "Baby, I know js. Js is very fun!"] 
console.log(jsPatternWithGFlag.exec(str));
// 15
console.log(jsPatternWithGFlag.lastIndex);
 
// ["Js", index: 17, input: "Baby, I know js. Js is very fun!"] 
console.log(jsPatternWithGFlag.exec(str));
// 19
console.log(jsPatternWithGFlag.lastIndex);

Обратите внимание, что свойство lastIndex доступно для записи, что позволяет самому задать позицию, с которой будет производиться поиск совпадений.

var str = "Baby, I know js. Js is very fun!",
  jsPatternWithGFlag = /js/ig;
 
jsPatternWithGFlag.lastIndex = 15;
 
// ["Js", index: 17, input: "Baby, I know js. Js is very fun!"] 
console.log(jsPatternWithGFlag.exec(str));
// 19
console.log(jsPatternWithGFlag.lastIndex);

Метод test объекта RegExp

Этот метод существует для проверки соответствуют ли строка регулярному выражению. Он возвращает true, если строка соответствует регулярному выражению и false в противном случае.

var strOne = "Rock'n'roll heaven 24/7",
  strTwo = "Roses are red",
  digitPattern = /\d/;
 
console.log(digitPattern.test(strOne)); // true
console.log(digitPattern.test(strTwo)); // false

Этот метод также устанавливает свойство lastIndex если регулярное выражение имеет флаг g, и при следующем его вызове проверка совпадения начинается с новой позиции.

var str = '2 литра "Жигулёвского" и пачку "Винстона", пожалуйста',
  digitPattern = /\d/g;
 
digitPattern.test(str); // true
console.log(digitPattern.lastIndex); // 1
 
digitPattern.test(str); // false

Объект RegExp и литералы регулярных выражений

Как упоминалось ранее, регулярные выражения можно создавать двумя способами: при помощи литералов регулярных выражений и при помощи конструктора объекта RegExp. Разные браузеры по-разному ведут себя с литералами регулярных выражений. Большинство современных браузеров создают новый объект RegExp, каждый раз, когда встречается литерал регулярного выражения, но возможно и ситуация, когда объект RegExp создаётся единожды в ходе синтаксического анализа. Эти различая очень важны, если вы используете методы объекта RegExp c флагом g и свойство lastIndex.

На этом пока всё. Как всегда желаю вам успехов!

  • Очень полезная информация спасибо!

  • Анастасия

    Да, спссибо, но что произошло? Читала в захлеб, а потом раз…и страницы кончились. Вы очень хорошо приподносите информацию

  • Тимур Абрамов

    У вас очень хорошие уроки. Когда будут следующие?