Выбрать главу

Простой пример:

var s = "the cia and fbi";

console.log(s.replace(/\b(fbi|cia)\b/g, function(str) {

  return str.toUpperCase();

}));

// → the CIA and FBI

А вот более интересный:

var stock = "1 lemon, 2 cabbages, and 101 eggs";

function minusOne(match, amount, unit) {

  amount = Number(amount) - 1;

  if (amount == 1) // остался только один, удаляем 's' в конце

    unit = unit.slice(0, unit.length - 1);

  else if (amount == 0)

    amount = "no";

  return amount + " " + unit;

}

console.log(stock.replace(/(\d+) (\w+)/g, minusOne));

// → no lemon, 1 cabbage, and 100 eggs

Код принимает строку, находит все вхождения чисел, за которыми идёт слово, и возвращает строчку, где каждое число уменьшено на единицу.

Группа (\d+) попадает в аргумент amount, а (\w+) – в unit. Функция преобразовывает amount в число – и это всегда срабатывает, потому что наш шаблон как раз \d+. И затем вносит изменения в слово, на случай, если остался всего один предмет или ни одного.

Жадность

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

function stripComments(code) {

  return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");

}

console.log(stripComments("1 + /* 2 */3"));

// → 1 + 3

console.log(stripComments("x = 10;// ten!"));

// → x = 10;

console.log(stripComments("1 /* a */+/* b */ 1"));

// → 1  1

Часть перед оператором «или» совпадает с двумя слэшами, за которыми идёт любое количество символов, кроме символов перевода строки. Часть, убирающая многострочные комментарии, более сложна. Мы используем , т. е. любой символ, не являющийся пустым, в качестве способа найти любой символ. Мы не можем использовать точку, потому что блочные комментарии продолжаются и на новой строке, а символ перевода строки не совпадает с точкой.

Но вывод предыдущего примера неправильный. Почему?

Часть сначала попытается захватить столько символов, сколько может. Если из-за этого следующая часть регулярки не найдёт себе совпадения, произойдёт откат на один символ и попробует снова. В примере, алгоритм пытается захватить всю строку, и затем откатывается. Откатившись на четыре символа назад, он найдёт в строчке / — а это не то, чего мы добивались. Мы-то хотели захватить только один комментарий, а не пройти до конца строки и найти последний комментарий.

Из-за этого мы говорим, что операторы повторения (+, *, ?, and {}) жадные, то есть они сначала захватывают, сколько могут, а потом идут назад. Если вы поместите вопрос после такого оператора (+?, *?, ??, {}?), они превратятся в нежадных, и начнут находить самые маленькие из возможных вхождений.

И это то, что нам нужно. Заставив звёздочку находить совпадения в минимально возможном количестве символов строчки, мы поглощаем только один блок комментариев, и не более того.

function stripComments(code) {

  return code.replace(/\/\/.*|\/\*[^]*?\*\//g, "");

}

console.log(stripComments("1 /* a */+/* b */ 1"));

// → 1 + 1

Множество ошибок возникает при использовании жадных операторов вместо нежадных. При использовании оператора повтора сначала всегда рассматривайте вариант нежадного оператора.

Динамическое создание объектов RegExp

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

Но вы можете построить строку и использовать конструктор RegExp. Вот пример:

var name = "гарри";

var text = "А у Гарри на лбу шрам.";

var regexp = new RegExp("\\b(" + name + ")\\b", "gi");

console.log(text.replace(regexp, "_$1_"));

// → А у _Гарри_ на лбу шрам.

При создании границ слова приходится использовать двойные слэши, потому что мы пишем их в нормальной строке, а не в регулярке с прямыми слэшами. Второй аргумент для RegExp содержит опции для регулярок – в нашем случае “gi”, т. е. глобальный и регистронезависимый.

Но что, если имя будет "dea+hl[]rd" (если наш пользователь – кульхацкер)? В результате мы получим бессмысленную регулярку, которая не найдёт в строке совпадений.

Мы можем добавить обратных слэшей перед любым символом, который нам не нравится. Мы не можем добавлять обратные слэши перед буквами, потому что \b или \n – это спецсимволы. Но добавлять слэши перед любыми не алфавитно-цифровыми символами можно без проблем.