[Перевод] Основы регулярных выражений в JavaScript
Если вы иногда поглядываете на регулярные выражения, но всё никак не решаетесь их освоить, думая, что всё это невероятно сложно — знайте — вы не одиноки. Для любого, кто не понимает, что такое регулярные выражения, или не разбирается в том, как они работают, они выглядят как совершенная бессмыслица.
Мощная картинка для привлечения внимания :) Осторожно, может засосать!
Но, на самом деле, регулярные выражения — это мощный инструмент, который может помочь вам сэкономить уйму времени. В этом материале мы рассмотрим основы регулярных выражений в 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, в обычное предложение и добавляет после его последнего слова точку.
Уважаемые читатели! Если у вас получилось написать функцию, о которой только что шла речь — предлагаем поделиться её кодом в комментариях. Кроме того, если вы хорошо знакомы с регулярными выражениями, просим рассказать о том, помогло ли вам это знакомство в реальных проектах.
Ну и топ Хабрапостов про регулярные выражения.