Перевод статьи «How to use Regular Expressions in JavaScript».
«Некоторые люди, встречаясь с какой-то проблемой в программировании, думают, что это подходящий случай для использования регулярных выражений. В результате они получают две проблемы», — Джейми Завински (хакер мирового уровня).
Что такое регулярные выражения
Регулярное выражение (regular expression, RegEx) — это строка текста, позволяющая создавать шаблоны для поиска соответствия и управления текстом. Те, кто овладели этой черной магией, могут очень многое делать при помощи RegEx. Для остальных регулярные выражения могут стать источником замешательства и неразберихи. Примерно так я к ним и относилась до недавнего времени.
Но недавно, практикуясь в алгоритмах для прохождения собеседования, я получше присмотрелась к регулярным выражениям. Оказалось, что они по крайней мере не такие сложные, как я считала раньше, и могут быть невероятно полезными.
Эта тема очень обширна, и ее совершенно невозможно вместить в одну статью. Поэтому я просто поделюсь несколькими ключевыми вещами, которые буквально открыли мне глаза на то, насколько мощными могут быть регулярные выражения.
При помощи RegEx можно проверить, есть ли совпадение с шаблоном в строке
Представьте, что вам нужно узнать, содержится ли в строке определенное слово. Вы можете просто сделать вот так:
const string = 'The cat sat on the mat' const regex = /sat/ regex.test(string) // result: true
Этот код «тестирует» строку, проверяя, есть ли в ней слово «sat».
Две косые черты // во второй строке говорят JavaScript, что символы между ними — часть регулярного выражения. Для проверки строки можно просто скомбинировать переменную RegEx с методом test().
В результате мы получим булево значение (true или false). Его запросто можно использовать с предложением if/else или тернарным оператором, чтобы дальнейшие действия зависели от того, есть ли нужное слово в строке.
Использование с блоком if/else:
const string = 'The cat sat on the mat' const regex = /sat/ if (regex.test(string)) { 'The word sat can be found in the string' } else { 'The word sat is not in the string' } // result: 'The word sat can be found in the string'
Использование с тернарным оператором:
const string = 'The cat sat on the mat' const regex = /sat/ const result = regex.test(string) ? 'The word sat can be found in the string' : 'The word sat is not in the string' // result: 'The word sat can be found in the string'
Этот пример можно расширить: RegEx может включать «i» в конце выражения:
/sat/i
Это сделает проверку нечувствительной к регистру. Таким образом нужное слово совпадет с шаблоном, даже если будет написано с использованием заглавных букв.
Возврат совпавших символов, а не просто true или false
Что, если для дальнейшего использования вам нужно не просто проверить, есть ли совпадение в строке, а захватить его?
Это можно сделать при помощи метода match(). Обратите внимание, что здесь синтаксис немного отличается (в скобках — регулярное выражение, а не строка).
const string = '989hjk976' const regex = /[a-z]/gi console.log(string.match(regex)) // result: [h, j, k]
Квадратные скобки задают диапазон символов (в данном случае — любые буквы английского алфавита в нижнем регистре). Все, что попадает в этот диапазон, будет соответствовать шаблону. Диапазоны символов могут быть разными. Это могут быть буквы в верхнем регистре (тогда диапазон будет выглядеть как [A-Z]) или цифры ([0-9]).
Если ваш диапазон должен охватывать все буквенно-цифровые символы и символ подчеркивания, то шаблон может выглядеть следующим образом: [a-zA-Z0-9_]. Но это можно записать короче, использовав сокращение \w (в английском варианте общее название таких символов — word characters, — прим. перев.).
Вернемся к нашему примеру. Буква «g» после косой черты означает «global». Она указывает, что нужно показать все совпадения, а не только первое. Дело в том, что RegEx при поиске читает слева направо и останавливается, найдя первое совпадение, если прямо не указать другое поведение.
Если вам нужно быть более конкретным, вы можете использовать и другие флаги с переключателями:
Знак «+»
const string = 'abc123DEF' const regex = /[a-zA-Z]+/g console.log(string.match(regex)) // result: ['abc', DEF] //Обратите внимание: + означает "1 или больше подряд"
Знак «.»
const string = 'abc123DEF' const regex = /[a-z]./g console.log(string.match(regex)) // result: ['ab', 'c1'] // Символ '.' означает: 'Еще один любой символ, идущий после совпадения с паттерном'.
Знак «^»
const onlyReturnIfConsonant = (str) => { const regex = /^[^aeiou]/ const result = str.match(regex) console.log(result) } // onlyReturnIfConsonant("bananas"); // result: ['b'] // onlyReturnIfConsonant("email"); // result: null
Символ «^» ВНЕ [ ] означает проверку НАЧАЛА строки.
Символ «^» ВНУТРИ [ ] служит для указания, что искать нужно любой символ, кроме указанных в квадратных скобках. Шаблону в этом примере будут соответствовать только слова, начинающиеся с согласных букв.
Порядок расположения символов имеет значение, так что при составлении регулярного выражения следует быть внимательным.
Есть много других флагов и переключателей, которые могут при необходимости использоваться в сочетаниях. Я просто привела примеры, чтобы показать возможности. Более подробно о RegEx и match() можно почитать в этой статье.
Форматирование строки с использованием RegEx и split()
Представьте, что вам нужно не просто захватить совпадение с шаблоном, а одновременно как-то его переформатировать. Один из возможных сценариев — с использованием метода split(). Этот метод делит строку на упорядоченный список подстрок и возвращает их в массиве. Это может быть очень полезно, но как определить принцип, по которому разбивать строку? Здесь вам пригодятся RegEx. Следующий пример показывает потенциальный вариант использования регулярных выражений внутри функции:
const separateAString = (str) => { return str.split(/\s+|\_+|(?=[A-Z])/).join(' ') } separateAString('TheCat_Sat onTheMat'); // result: ['The', 'Cat', 'Sat', 'On', 'The', 'Mat'] (до join()) // result: 'The Cat Sat On The Mat' (после join(" "), с включенными теперь пробелами)
Мы видим, что при помощи RegEx удалось переформатировать строку. Но что, черт побери, означает вот это?
/\s+|\_+|(?=[A-Z])/
\s
ищет любые пробелы (а +
означает «один или больше»).
_
ищет символы подчеркивания. Здесь мы видим пример экранированного символа, то есть такого, который определен буквально, а не в специальном значении. Остановимся на этом подробнее.
Если в регулярном выражении используется буква «s», она трактуется именно как буква. Но если перед ней стоит обратный слеш — \s
— это уже означает любой пробельный символ. Есть специальные символы, которые по умолчанию используются в RegEx для составления сложных конструкций: [ \ ^ $ . | ? * + ( )
. Чтобы использовать такой символ в шаблоне в буквальном значении, его нужно экранировать. Например, чтобы искать символ «^», нужно написать \^
.
Здесь это не абсолютно необходимо (символ подчеркивания в JavaScript не нужно экранировать), просто использовано для примера.
Знак +
использован, чтобы захватывать одно или больше вхождений символа подчеркивания.
Скобки ()
означают захват группы — это способ указывать несколько символов как отдельный юнит.
?=[A-Z]
в скобках — пример положительной опережающей проверки, которая в данном случае означает «Раздели строку непосредственно перед любой буквой в верхнем регистре».
Символ |
в регулярных выражениях означает «или». Здесь он использован для указания вариантов разделения: «Раздели строку, где есть пробел ИЛИ есть символ подчеркивания, ИЛИ перед заглавной буквой». Возможность составлять цепочки из различных частей — одна из составляющих могущества RegEx.
Метод join() завершает процесс, конвертируя массив обратно в строку. В качестве аргумента в join() передано ' '
(пробел в кавычках). В результате к каждой подстроке, выделенной методом split(), будет добавлен пробел.
Внесение правок с использованием RegEx и replace()
И последний пример. Представьте, что вам нужно найти что-то в строке и тут же заменить найденное на что-то другое. Это можно сделать при помощи метода replace().
Вот базовый пример использования replace() внутри функции:
const replaceExample = (str) => { return str.replace('Test', 'Game') } replaceExample('This is a Test'); // result: 'This is a Game'
Этот метод принимает два аргумента: первый — часть строки, которую нужно заменить, второй — чем заменить.
Аргументы могут быть как строками, так и регулярными выражениями. Если в качестве первого аргумента используется строка (как в приведенном примере), будет заменено только первое вхождение этой строки. Поэтому, если вы хотите заменить все вхождения, вам очень пригодятся RegEx (вспомните о флаге «g»).
Следующий пример демонстрирует использование регулярных выражений с методом replace():
const separateStrings = (str) => { return str.replace(/([a-z])([A-Z])/g, '$1 $2') } separateStrings('AnotherStringToSeparate'); // result: 'Another String To Separate'
Здесь мы видим новую технику.
В нашем примере есть две группы (это то, что в круглых скобках). В первой группе у нас все символы английского алфавита в нижнем регистре, а во второй — в верхнем.
Второй параметр — $1 $2
— это прямая ссылка на эти группы. $1 — ссылка на первую группу — ([a-z])
, а $2 — на вторую — ([A-Z])
. Мы взяли их в кавычки и поставили между ними пробел. Теперь это означает: «В тех местах, где буква в нижнем регистре соседствует с буквой в верхнем регистре, поставь между ними пробел».
Если вы укажете в качестве второго аргумента $1-$2
, то в строке между словами будут поставлены дефисы и получится что-то вроде «Another-String-To-Separate». Это довольно динамичный функционал, открывающий много возможностей.
Конечно, вы можете не только добавлять пробелы или символы. В следующем примере показано, как можно определить две группы, а затем поменять их местами:
const shuffleAWord = (str) => { return str.replace(/(^[^aeiou]+)(\w*)/, '$2$1'); } shuffleAWord("grain"); // result: 'aingr' // здесь '$1' - это 'gr', '$2' - это 'ain'
Первая группа (^[^aeiou]+)
захватывает все согласные буквы, идущие с начала слова до первой гласной. В нашем примере это возвращается как «gr».
Вторая группа захватывает все буквенно-цифровые символы (\w*)
, которые не захватила первая группа. Символ «*» означает «0 или больше вхождений» (помним, что «+» означал «1 или больше»). В нашем примере эта группа возвращает «ain».
Второй аргумент в методе replace — ссылки на группы (как и в предыдущем примере), только теперь мы поменяли их местами и не добавляли ни пробелов, ни символов между ними. В результате мы получаем «aingr».
Заключение
Приведенные в этой статье примеры надуманны. Их цель — показать, насколько настраиваемыми и гибкими могут быть регулярные выражения при использовании вместе с методами JavaScript.
В завершение статьи дам несколько советов:
- Несмотря на всю их полезность, не следует злоупотреблять RegEx, поскольку они могут ухудшить читаемость кода.
- Если вам кажется, что RegEx может запутать читателя, добавьте комментарий к коду, поясняющий, что она делает.
- Следите за тем, чтобы ваши регулярные выражения были как можно более краткими и понятными.
- Конструирование RegEx может быть сложной задачей, но для этого есть специальные инструменты, сильно облегчающие процесс. Например, regex101.com и regexr.com.
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]