Как использовать регулярные выражения в JavaScript

0
807
views

Перевод статьи «How to use Regular Expressions in JavaScript».

Photo by Isaac Chou on Unsplash

«Некоторые люди, встречаясь с какой-то проблемой в программировании, думают, что это подходящий случай для использования регулярных выражений. В результате они получают две проблемы», — Джейми Завински (хакер мирового уровня).

Что такое регулярные выражения

Регулярное выражение (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.

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here