[Перевод] Основы регулярных выражений в JavaScript

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

edj5fr1qlmyse5n_igkfhdt9bx4.png
Мощная картинка для привлечения внимания :) Осторожно, может засосать!

Но, на самом деле, регулярные выражения — это мощный инструмент, который может помочь вам сэкономить уйму времени. В этом материале мы рассмотрим основы регулярных выражений в JavaScript.

Создание регулярных выражений в JS


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

Существует два способа создания регулярных выражений.

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

var regexLiteral = /cat/;


Второй задействует конструктор объекта RegExp, которому передают строку, из которой он создаёт регулярное выражение:

var regexConstructor = new RegExp("cat");


В обоих вышеприведённых примерах создаётся один и тот же шаблон — символ c, за которым следует символ a, за которым идёт символ t.

Какой способ создания регулярных выражений выбрать? Тут стоит придерживаться такого правила: если вы предполагаете пользоваться регулярным выражением так, что оно остаётся неизменным — лучше использовать литерал. Если ваше регулярное выражение является динамическим, оно может меняться в ходе выполнения программы, лучше использовать конструктор RegExp.

Методы регулярных выражений


Выше вы могли заметить, что регулярные выражения в JS — это объекты. У объектов, как известно, есть методы, и регулярные выражения — не исключение.

Один из основных методов регулярных выражения — это .test(), который возвращает логическое значение:

RegExp.prototype.test()


А именно, этот метод возвращает true если строка содержит совпадение с заданным шаблоном регулярного выражения. Если совпадений не найдено — он возвращает false.

Рассмотрим следующий пример. У нас имеется две строки и одно регулярное выражение. Мы можем использовать регулярное выражение для проверки того, встречается ли заданный текстовый шаблон в строках:

const str1 = "the cat says meow";
const str2 = "the dog says bark";
const hasCat = /cat/;
hasCat.test(str1);
// true
hasCat.test(str2);
// false


Как и ожидается, когда мы проверяем первую строку, str1, на наличие в ней последовательности символов cat, мы получаем true. А вот проверив вторую строку, str2, мы в ней cat не находим, поэтому метод .test() возвращает false.

Базовые конструкции регулярных выражений


К счастью (или к несчастью — это уж кому как), основной подход к изучению регулярных выражений заключается в том, чтобы запоминать основные конструкции, обозначающие символы и группы символов.

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

▍Символы


  • . (точка) — соответствует любому одиночному символу за исключением переноса строки.
  • * — соответствует предыдущему выражению, которое повторено 0 или более раз.
  • + — соответствует предыдущему выражению, которое повторено 1 или более раз.
  • ? — соответствует предыдущему выражению, повторённому 0 или 1 раз.
  • ^ — соответствует началу строки.
  • $ — соответствует концу строки.


▍Группы символов


  • \d — соответствует любому одиночному цифровому символу.
  • \w — соответствует любому символу — цифре, букве, или знаку подчёркивания.
  • [XYZ] — набор символов. Соответствует любому одиночному символу из набора, заданного в скобках. Кроме того, похожим образом можно задавать и диапазоны символов, например — [A-Z].
  • [XYZ]+ — соответствует символу из скобок, повторённому один или более раз.
  • [^A-Z] — внутри выражений, задающих диапазоны символов, символ ^ используется как знак отрицания. В данном примере шаблону соответствует всё, что не является буквами в верхнем регистре.


▍Флаги


Существует пять необязательных флагов регулярных выражений. Они могут использоваться совместно или раздельно, их помещают после закрывающего слэша. Регулярные выражения с флагами выглядят так: /[A-Z]/g. Мы рассмотрим тут лишь два флага:

  • g — глобальный поиск по строке.
  • i — поиск, нечувствительный к регистру.


▍Дополнительные конструкции


  • (x) — захватывающие скобки. Это выражение соответствует x и запоминает это соответствие, в результате, мы можем воспользоваться им позже.
  • (?:x) — незахватывающие скобки. Выражение соответствует x, но не запоминает это соответствие
  • x(?=y) — упреждающее соответствие. Соответствует x только если за ним следует y.


Более сложные примеры регулярных выражений


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

Для начала проверим строку на наличие в ней любых цифр. Для того, чтобы это сделать, мы можем использовать шаблон \d. Взгляните на нижеприведённый код. Он возвращает true в тех случаях, когда в исследуемой строке имеется хотя бы одна цифра.

console.log(/\d/.test('12-34'));
// true


Как видите, код возвращает true — это неудивительно, так как в исследуемой строке есть четыре цифровых символа.

А что если нам нужно проверить строку на наличие в ней некоей последовательности цифровых символов? В подобном случае можно воспользоваться шаблоном \d, повторённым несколько раз. Например, для того, чтобы регулярное выражение соответствовало строке 11, можно воспользоваться конструкцией \d\d, которая описывает два любых последовательно расположенных цифровых символа. Взгляните на этот код:

console.log(/\d-\d-\d-\d/.test('1-2-3-4'));
// true
console.log(/\d-\d-\d-\d/.test('1-23-4'));
// false


Как видно, тут мы проверяем строку на то, имеются ли в ней последовательности одиночных цифр, разделённых чёрточками. Первая строка такому шаблону соответствует, а вторая — нет.

Как быть, если неважно сколько именно цифр находится до или после чёрточек, если их количество больше или равняется единице? В подобной ситуации можно воспользоваться знаком + для того, чтобы указать, что шаблон /d может встречаться один или более раз. Вот как это выглядит:

console.log(/\d+-\d+/.test('12-34'));
// true
console.log(/\d+-\d+/.test('1-234'));
// true
console.log(/\d+-\d+/.test('-34'));
// false


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

console.log(/me+(ow)+w/.test('meeeeowowoww'));
// true


Получилось. Теперь давайте рассмотрим это выражение подробнее. На самом деле, тут происходит много интересного.

Итак, вот регулярное выражение.

/me+(ow)+w/


  • m — соответствует одиночной букве m.
  • e+ — соответствует букве e, повторённой один или более раз.
  • (ow)+ соответствует сочетанию ow, повторённому один или более раз.
  • w — соответствует одиночной букве w.


В результате это выражение воспринимает строку следующим образом:

'm' + 'eeee' +'owowow' + 'w'


Как видите, если операторы вроде + используются сразу после выражений, заключённых в скобки, они относятся ко всему тому, что находится в скобках.

Вот ещё один пример, он касается использования оператора ?. Знак вопроса указывает на то, что присутствие предшествующего ему символа в строке необязательно.

Взгляните на это:

console.log(/cats? says?/i.test('the Cat says meow'));
// true
console.log(/cats? says?/i.test('the Cats say meow'));
// true


Как видите, каждое из выражений возвращает true. Это происходит потому что мы сделали символы s в конце последовательностей cat и say необязательными. Кроме того, можно заметить, что в конце регулярного выражения находится флаг i. Благодаря ему при анализе строк игнорируется регистр символов. Именно поэтому регулярное выражение реагирует и на строку cat, и на строку Cat.

Об экранировании служебных символов


Регулярные выражения заключают в слэши. Кроме того, некоторые символы, вроде +, ?, и другие, имеют особый смысл. Если вам нужно организовать поиск в строках этих особых символов, их нужно экранировать с помощью обратного слэша. Вот как это выглядит:

var slash = /\//;
var qmark = /\?/;


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

  • \d — это то же самое, что и [0-9]. Каждое из этих выражений соответствует любому цифровому символу.
  • \w — это то же самое, что [A-Za-z0-9_]. И то и другое найдёт в строке любой одиночный алфавитно-цифровой символ или знак подчёркивания.


Проект №1: добавление пробелов в строки, построенные в верблюжьемСтиле


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

removeCc('camelCase') // => возвратит 'camel Case'


Для начал надо написать каркас функции, которая принимает строку и возвращает новую строку:

function removeCc(str){
  // вернуть новую строку
}


Теперь нам нужно лишь записать в выражение return этой функции некую конструкцию, которая использует регулярные выражения, обрабатывающие входные данные. Для того, чтобы это сделать, сначала нужно найти в строке все заглавные буквы, используя, во-первых, конструкцию, задающую диапазон символов, во-вторых — флаг i, обеспечивающий глобальный поиск в строке.

/[A-Z]/g


Это регулярное выражение отреагирует на букву C в строке camelCase. А как добавить пробел перед этой буквой C?

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

// Захватывающие скобки
/([A-Z])/
// Работа с сохранённым значением
$1


Тут вы можете видеть, что мы используем конструкцию $1 для обращения к захваченному значению. Стоит отметить, что если в выражении имеется два набора захватывающих скобок, можно пользоваться выражениями $1 и $2 для того, чтобы ссылаться на захваченные значения в порядке их следования слева направо. При этом захватывающие скобки можно использовать столько раз, сколько нужно в конкретной ситуации.

Обратите внимание на то, что нам не нужно захватывать значение в скобках. Можно и не использовать его, или использовать незахватывающие скобки с помощью конструкции вида (?:x). В данном примере находится соответствие с x, но оно не запоминается.

Вернёмся к нашему проекту. Есть метод объекта String, который можно использовать для работы с захватывающими скобками — это .replace(). Для того, чтобы им воспользоваться, мы будем искать в строке любые заглавные буквы. Вторым аргументом метода, представляющим заменяющее значение, будет сохранённое значение:

function removeCc(str){
  return str.replace(/([A-Z])/g, '$1');  
}


Мы уже близки к решению, хотя цели пока ещё не достигли. Взглянем снова на наш код. Тут мы захватываем заглавные буквы, затем меняем их на эти же буквы. А нам надо, чтобы перед ними оказались пробелы. Сделать это довольно просто — достаточно добавить пробел перед переменной $1. В результате перед каждой заглавной буквой в строке, которую возвратит функция, будет пробел. В итоге у нас получилось следующее:

function removeCc(str){
  return str.replace(/([A-Z])/g, ' $1');  
}
removeCc('camelCase') // 'camel Case'
removeCc('helloWorldItIsMe') // 'hello World It Is Me'


Проект №2: удаление заглавных букв из строки


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

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

Итак, первое что нам надо — выбрать все заглавный буквы в строке. Тут используется та же конструкция, что и в предыдущем примере:

/[A-Z]/g


Тут же мы будем использовать и уже знакомый вам метод .replace(), но в этот раз нам, при вызове этого метода, понадобится кое-что новое. Вот как будет выглядеть схема того, что нам нужно. Знаки вопроса указывают на этот новый, пока неизвестный, код:

function lowerCase(str){
  return str.replace(/[A-Z]/g, ???);  
}


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

Если мы ещё и используем флаг глобального поиска, функция будет вызываться для каждого совпадения с шаблоном, найденного в строке. Помня об этом, мы можем воспользоваться методом .toLowerCase() объекта String для преобразования входной строки к нужному нам виду. Вот как, с учётом вышесказанного, выглядит решение нашей задачи:

function lowerCase(str){
  return str.replace(/[A-Z]/g, u => u.toLowerCase());
}
lowerCase('camel Case') // 'camel case'
lowerCase('hello World It Is Me') // 'hello world it is me'


Проект №3: преобразование к верхнему регистру первую букву первого слова строки


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

capitalize('camel case') // => должна быть возвращена строка 'Camel case'


Тут, как и прежде, будем пользоваться методом .replace(). Однако, в этот раз нам нужно найти лишь самый первый символ строки. Для того, чтобы это сделать, задействуем символ ^. Вспомним один из вышеприведённых примеров:

console.log(/cat/.test('the cat says meow'));
// true


Если добавить в начало шаблона символ ^, true эта конструкция уже не возвратит. Произойдёт это из-за того, что слово cat находится не в начале строки:

console.log(/^cat/.test('the cat says meow'));
// false


Нам надо, чтобы специальный символ ^ воздействовал на любой символ в нижнем регистре, находящийся в начале строки. Поэтому мы добавляем его прямо перед конструкцией [a-z]. В результате регулярное выражение отреагирует только на первую букву строки в нижнем регистре:

/^[a-z]/


Кроме того, тут мы не используем флаг глобального поиска, так как нам нужно найти лишь одно совпадение с шаблоном. Теперь всё, что осталось сделать — это преобразовать найденный символ к верхнему регистру. Сделать это можно с помощью строкового метода .toUpperCase():

function capitalize(str){
  return str.replace(/^[a-z]/, u => u.toUpperCase());
}
capitalize('camel case') // 'Camel case'
capitalize('hello world it is me') // 'Hello world it is me'


Совместное использование ранее созданных функций


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

function removeCc(str){
  return str.replace(/([A-Z])/g, ' $1');  
}
function lowerCase(str){
  return str.replace(/[A-Z]/g, u => u.toLowerCase());
}
function capitalize(str){
  return str.replace(/^[a-z]/, u => u.toUpperCase());
}
capitalize(lowerCase(removeCc('camelCaseIsFun')));
// "Camel case is fun"


Итоги


Как видите, хотя регулярные выражения и выглядят для неподготовленного человека весьма непривычно, их вполне можно освоить. Лучший способ изучения регулярных выражений — практика. Предлагаем вам попробовать следующее: напишите, на основе трёх созданных нами функций, одну, которая преобразует переданную ей строку, вроде camelCase, в обычное предложение и добавляет после его последнего слова точку.

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

Ну и топ Хабрапостов про регулярные выражения.

© Habrahabr.ru