Синтаксис регулярных выражений

Регулярное выражение описывает шаблон текста: какие символы искать, сколько раз они могут повторяться и что должно находиться рядом. В JavaScript шаблоны используют для поиска, проверки строк, извлечения частей текста и замены. Здесь собраны записи языка шаблонов, объект и его методы описаны в справочнике RegExp.

Шаблоны можно проверить в тестере регулярных выражений.

Быстрый переход

Запись шаблона и экранирование

Литерал записывают между косыми чертами: /шаблон/флаги. Конструктор принимает текст шаблона и флаги отдельно, без внешних косых черт. Следующие записи ищут последовательность из одной или нескольких цифр от 0 до 9 подряд — например, 123 в строке "Заказ 123". Здесь \d обозначает цифру, а + — одно или больше повторений:

JavaScript
// Ищем одну или несколько цифр подряд, например 123 в строке "Заказ 123".
const literal = /\d+/u;
const fromString = new RegExp("\\d+", "u");

console.log(literal.test("Заказ 123"));    // true
console.log(fromString.test("Заказ 123")); // true

В обычной строке JavaScript обратную косую черту удваивают: "\\d" передаёт конструктору \d. Ошибка в литерале приводит к SyntaxError при разборе скрипта; ошибка в строковом шаблоне — при вызове конструктора. Вне квадратных скобок специальный смысл имеют ^ $ \ . * + ? ( ) [ ] { } |. Для буквального поиска поставьте перед таким символом \.

Как записать специальные символы
Нужно найти Литерал Конструктор
Точку /\./u new RegExp("\\.", "u")
Обратную косую черту /\\/u new RegExp("\\\\", "u")
Косую черту /\//u new RegExp("/", "u")
Перевод строки /\n/u new RegExp("\\n", "u")

Косая черта / ограничивает литерал, поэтому в нём её экранируют. Обычный пробел значим: /a b/ ищет пробел между буквами. В строковых шаблонах с String.raw не нужно удваивать обратные косые черты, но подставленные данные всё равно требуют экранирования.

Подстановка текста в шаблон

RegExp.escape() превращает произвольный текст в буквальную часть шаблона: точка или скобка из данных не станет оператором. Метод нужен для текста, а не для уже готового регулярного выражения. В старых средах его может не быть; проверьте поддержку перед применением. Для простого поиска подстроки без шаблона достаточно includes().

JavaScript
const prefix = "item.v2";
// Ищем всю строку: буквальный префикс "item.v2", дефис и одну или несколько цифр.
// RegExp.escape() экранирует точку в префиксе: она означает именно точку.
const pattern = new RegExp("^" + RegExp.escape(prefix) + "-\\d+$", "u");

console.log(pattern.test("item.v2-42")); // true
console.log(pattern.test("itemXv2-42")); // false: вместо точки стоит X

Символы и классы символов

Символы и классы символов
Запись Значение Пример и результат
abc Обычные символы совпадают сами с собой. /чай/ в «зелёный чай»
→ чай
. Любой символ, кроме разделителей строк. Флаг s снимает это исключение. /ч.й/g в «чай, чей»
→ чай, чей
\d, \D Цифра 0–9 и любой символ, кроме неё. /\d+/ в «TK-101»
→ 101
\w, \W Символ из [A-Za-z0-9_] и его отрицание. Кириллица не входит в \w. Особенность флага i описана ниже. /\w+/ в «tea_1 чай»
→ tea_1
\s, \S Пробельный символ (пробел, табуляция, перевод строки, неразрывный пробел) и всё остальное. /\s+/ в «чай мёд»
→ один пробел
[abc] Один символ из набора. /[чм]ай/g в «май, чай»
→ май, чай
[a-z], [а-яё] Диапазон по кодам символов, а не по алфавиту языка. Буква ё не входит в а-я — добавляйте её отдельно. /[а-яё]+/ в «ёж»
→ ёж
[^abc] Один символ вне набора. ^ отрицает набор только сразу после [. /[^\d ]+/ в «12 шт.»
→ шт.
\., \\, \[ Экранирование: специальный символ как обычный. Например, \. ищет точку, а не произвольный символ. /\d+\.\d+/ в «цена 12.50»
→ 12.50
\n, \t, \u{1F36F} Перевод строки, табуляция, символ по коду (\u{…} — с флагом u или v). /\u{1F36F}/u в «мёд🍯»
→ 🍯

Внутри […] точка, *, +, ? и $ обычно буквальные. Для ], \ и буквального дефиса используйте \], \\ и \-. Запись [|] ищет вертикальную черту, а [\b] — символ возврата на шаг U+0008, не границу слова. В режиме v действуют дополнительные правила экранирования.

Класс \w не становится классом всех букв с флагом u. При сочетании i с u или v он дополнительно включает символы, сводимые при Unicode-сопоставлении регистра к ASCII-буквам, например знак Кельвина K. Для букв разных письменностей используйте \p{L}.

Управляющие символы и коды

Записи символов по коду
Запись Значение Пример
\n, \r, \t Перевод строки LF, возврат каретки CR, табуляция. /\r?\n/ ищет перенос LF или CRLF.
\f, \v, \0 Перевод страницы, вертикальная табуляция, нулевой символ. После \0 не ставят цифру. Для явного нулевого кода можно использовать \x00.
\xHH, \uHHHH Шестнадцатеричный код из двух или четырёх цифр. /\x41\u0411/u совпадает с "AБ".
\u{H…} Кодовая точка Unicode; нужен u или v. /\u{1F36F}/u совпадает с "🍯".
\cA–\cZ Управляющие символы. \cI — табуляция, как \t.

Квантификаторы

Квантификатор задаёт число повторений предыдущего элемента. В ab+ повторяется только b, в (?:ab)+ — вся пара. Числа в {n,m} целые неотрицательные, n ≤ m; пробелы внутри не ставят.

Квантификаторы
Запись Значение Пример и результат
* 0 и больше раз. /ко*/g в «к, ко, коо»
→ к, ко, коо
+ 1 и больше раз. /\d+/ в «A-12»
→ 12
? 0 или 1 раз — необязательная часть. /цветы?/g в «цвет, цветы»
→ цвет, цветы
{n} Ровно n раз. /\d{3}/ в «TK-1010»
→ 101
{n,} n и больше раз. /\d{2,}/g в «1, 22, 333»
→ 22, 333
{n,m} От n до m раз. /\d{2,3}/ в «1234»
→ 123
*?, +?, ??, {n,}?, {n,m}? Ленивая форма: сначала пробует меньше повторений. Жадная форма сначала пробует больше. Обе меняют число повторений, если этого требует остальная часть шаблона. /<.+?>/g в «<b>чай</b>»
→ <b>, </b>

Ленивый поиск не обязательно даёт короткое совпадение: /a+?b/ найдёт целиком "aaab". Когда известен разделитель, часто понятнее исключить его: /"[^"\r\n]*"/ ищет текст в кавычках в пределах строки. Этот простой шаблон не разбирает экранированные кавычки.

Якоря и границы

Позиции в строке; сами символов не захватывают.

Якоря и границы
Запись Значение Пример и результат
^ Начало всего входного текста; с m — также позиция после разделителя строк. /^\d+/ в «12 шт.»
→ 12
$ Конец всего входного текста; с m — также позиция перед разделителем строк. /шт\.$/ в «12 шт.»
→ шт.
\b Граница между символом из \w и символом вне \w. За пределами строки считается, что символа слова нет. /\bTK\b/ в «TK-1, ATK»
→ только первое TK
\B Позиция, где \b не выполняется: с обеих сторон символы слова либо с обеих сторон их нет. /\Bk/ в «kick»
→ второе k

Для проверки всей строки обычно нужны оба якоря без m: /^\d{3}$/u принимает "123", но отвергает "A123", "1234" и "123\n". Граница \b не подходит для русских слов: /\bчай\b/u.test("чай") вернёт false. Можно явно запретить соседние буквы, комбинируемые знаки, цифры и подчёркивание:

JavaScript
const wholeWord = /(?<![\p{L}\p{M}\p{N}_])чай(?![\p{L}\p{M}\p{N}_])/gu;
console.log("чай, чайник, иван-чай".match(wholeWord)); // ["чай", "чай"]

Здесь дефис считается разделителем. Это правило конкретного поиска, а не универсальное определение слова. Для языкового разбиения текста используйте Intl.Segmenter. Поддержку (?<!…) уточняйте для целевых браузеров.

Группы и обратные ссылки

Объединение части шаблона и сохранение совпавшего текста.

Группы и обратные ссылки
Запись Значение Пример и результат
(…) Группа с захватом. Номера начинаются с 1 и следуют порядку открывающих скобок. /(\d+) шт/ в «12 шт.»
→ группа 1: 12
(?<имя>…) Именованная группа: имеет и имя, и номер. Значение доступно в свойстве groups результата. /(?<code>[A-Z]{2})-\d+/ в «TK-101»
→ groups.code: TK
(?:…) Группа без захвата: только объединяет, не сохраняет. /(?:чай|кофе)!/ в «кофе!»
→ кофе!, групп нет
a|b Альтернативы пробуются слева направо. Выбирается первая, с которой может совпасть весь шаблон. У | низкий приоритет. /^(чай|мёд)$/ в «мёд»
→ мёд
\1, \2 Обратная ссылка: тот же текст, что совпал в группе с этим номером. /(\w)\1/g в «coffee»
→ ff, ee
\k<имя> Обратная ссылка на именованную группу. /(?<q>["']).*?\k<q>/ в «"чай"»
→ "чай"

У | низкий приоритет: ^чай|мёд$ означает «чай в начале или мёд в конце». Для одного из двух значений целиком нужен ^(?:чай|мёд)$. А [чай|мёд] выберет один символ, в том числе саму черту. В строке "чайник" выражение /чай|чайник/ найдёт "чай".

В результате exec() элемент [0] содержит полное совпадение, [1] и далее — группы, groups — именованные группы. Неучаствовавшая группа имеет значение undefined. При повторении группы остаётся последний захват: для /(a)+/ и "aaa" группа 1 содержит "a", а не "aaa".

Обратная ссылка на существующую, но не участвовавшую в совпадении группу может совпасть с пустой строкой: /(a|(b))\2c/.test("ac") вернёт true.

Флаги для части шаблона

Группа (?i:…) включает нечувствительность к регистру только внутри неё, (?-i:…) — отключает. Доступны только i, m, s, например (?im-s:…). В /(?i:tea)-ID/ часть tea допускает любой регистр, а ID — только указанный. Модификатор не создаёт захватывающую группу. Это более новая возможность: перед использованием проверьте поддержку.

Опережающие и ретроспективные проверки

Опережающая проверка (англ. lookahead — взгляд вперёд) проверяет текст после текущей позиции, ретроспективная (англ. lookbehind — взгляд назад) — перед ней. Текст проверки не входит в полное совпадение.

Опережающие и ретроспективные проверки
Запись Значение Пример и результат
x(?=y) x, если после него идёт y. /\d+(?=₽)/ в «2 шт. по 120₽»
→ 120
x(?!y) x, если после него не идёт y. /\d+(?!\d|₽)/ в «2 шт. по 120₽»
→ 2
(?<=y)x x, если перед ним стоит y. /(?<=№)\d+/ в «заказ №1024»
→ 1024
(?<!y)x x, если перед ним не стоит y. /(?<![\d-])\d+/ в «-5 и 7»
→ 7

/\d+(?!₽)/ не означает «число без знака рубля»: для "120₽" оно найдёт "12", сократив совпадение. Условие (?!\d|₽) дополнительно запрещает следующую цифру и не даёт остановиться внутри числа. Аналогично (?<![\d-]) запрещает начать поиск внутри отрицательного числа.

Флаги

Флаги записывают после литерала или вторым аргументом конструктора. Порядок не важен, повторы запрещены: gi и ig равнозначны, gg вызывает SyntaxError. Флаги u и v нельзя включать вместе.

Флаги
Запись Значение Пример и результат
g Поиск всех совпадений в match() и replace(); последовательный поиск в exec(). Объект хранит позицию в lastIndex. /\d+/g в «1, 22»
→ 1, 22
i Без учёта регистра. /чай/i в «ЧАЙ»
→ ЧАЙ
m Многострочный режим: ^ и $ — границы каждой строки. /^\d/gm в «1 чай\n2 мёд»
→ 1, 2
s . совпадает и с переводом строки. /а.б/s в «а\nб»
→ всё
u Режим кодовых точек Unicode. Доступны \u{…} и \p{…}, синтаксис проверяется строже. Не объединяет составной эмодзи в одну кодовую точку. /^.$/u в «🍯»
→ совпадение есть
v Расширенный Юникод (вместо u): операции с наборами внутри [ ] и свойства строк. /[\p{L}--[a-z]]/v в «aб»
→ б
y Липкий поиск: совпадение только точно с позиции lastIndex. /\d/y в «12a»
→ 1 при исходном lastIndex = 0
d Добавляет indices к результату exec(): пары начала и конца совпадения и групп. Конечный индекс не включается. /(\d+)/d в «№12»
→ indices[1]: [1, 3]

Флаги m и s независимы: первый меняет якоря, второй — точку. Разделителями строк для этих правил служат LF, CR, U+2028 и U+2029. Индексы совпадений и lastIndex измеряются в кодовых единицах UTF-16 даже с u и v.

Состояние поиска: g, y и lastIndex

При вызовах exec() и test() флаги g и y делают поиск зависимым от lastIndex: повторная проверка той же строки может дать другой результат. Для независимых проверок обычно используют выражение без этих флагов. Правила изменения позиции и примеры приведены в разделе «Состояние поиска» объекта RegExp.

Свойства Юникода

Записи \p{…} и \P{…} требуют u или v: первая выбирает символы со свойством, вторая — без него. Имена чувствительны к регистру: Letter или L, но не letter. Эти классы выбирают символы, а не границы слов.

Свойства Юникода
Запись Значение Пример и результат
\p{L} Любая буква любой письменности. \P{L} — не буква. /\p{L}+/gu в «чай tea 茶»
→ три слова
\p{Script=Cyrillic} Символ с основным свойством письменности Cyrillic. Для символов нескольких письменностей есть Script_Extensions. /\p{Script=Cyrillic}+/u в «tea чай»
→ чай
\p{Lu}, \p{Ll} Заглавная или строчная буква любой письменности. /\p{Lu}/gu в «Чай и Мёд»
→ Ч, М
\p{Emoji_Presentation} Кодовая точка с отображением в виде эмодзи по умолчанию. Не означает любой составной эмодзи целиком. /\p{Emoji_Presentation}/u в «мёд🍯»
→ 🍯
[A--B], [A&&B] Только с v: разность и пересечение. Здесь A и B обозначают наборы. /[\p{L}&&\p{Script=Cyrillic}]/v в «a ж»
→ ж

Буквы, знаки и числа

Различия классов Unicode
Запись Значение Пример
\p{Nd} Десятичная цифра любой письменности. Полное имя — Decimal_Number. "12 ١٢".match(/\p{Nd}+/gu) → ["12", "١٢"]
\p{N} Числовой символ: цифры, дроби, римские числовые символы и другие числа. Полное имя — Number. "2 ½ Ⅳ".match(/\p{N}/gu) → ["2", "½", "Ⅳ"]
\p{M} Комбинируемый знак, например отдельный знак ударения. Не входит в \p{L}. "e\u0301".match(/[\p{L}\p{M}]+/u)[0] → "e\u0301"

Без u или v символ 🍯 занимает два элемента для точки и квантификаторов; с ними — одну кодовую точку. Но видимый символ может состоять из нескольких кодовых точек: /^.$/u.test("e\u0301") вернёт false. Эти флаги также не выполняют нормализацию: é и e\u0301 остаются разными последовательностями. При необходимости сначала примените normalize("NFC") к обоим сравниваемым текстам.

Дополнительные возможности флага v

Операции с наборами и свойства строк
Запись Значение Пример
[A--B] Из первого набора исключаются символы второго. "tea bread".match(/[[a-z]--[aeiou]]+/gv) → ["t", "br", "d"]
[\q{ab|cd}] Строки внутри класса. При выборе предпочитается более длинная. "2kg 5g".match(/[\q{kg|g}]/gv) → ["kg", "g"]
\p{RGI_Emoji} Эмодзи-последовательность из набора Unicode, в том числе несколько кодовых точек. "👨‍👩‍👧‍👦🍯".match(/\p{RGI_Emoji}/gv) → ["👨‍👩‍👧‍👦", "🍯"]

В одном уровне набора нельзя смешивать && и --; используйте вложенные квадратные скобки. В классе режима v буквальные ( ) [ ] { } / - | и обратную косую черту нужно экранировать. Зарезервированы и некоторые пары знаков, например !! и &&. Поэтому простая замена u на v может потребовать правки шаблона.

При сочетании с i отрицание свойств тоже различается: /\P{Lowercase_Letter}/iu.test("a") даёт true, а вариант с iv — false из-за другого порядка учёта регистра и отрицания. Свойства строк, такие как RGI_Emoji, нельзя отрицать через \P.

Строка замены

Эти записи действуют во втором аргументе replace() и replaceAll(), если передана строка. Они не являются синтаксисом самого регулярного выражения: в шаблоне обратная ссылка записывается \1, в замене — $1.

Специальные записи в строке замены
Запись Что вставляет Пример
$$ Один знак доллара. "12".replace(/\d+/, "$$") → "$"
$& Всё совпадение. "12".replace(/\d+/, "[$&]") → "[12]"
$1–$99 Группу с указанным номером. "120₽".replace(/(\d+)₽/, "$1 ₽") → "120 ₽"
$<name> Именованную группу. "TK-12".replace(/(?<n>\d+)/, "[$<n>]") → "TK-[12]"
$` Часть исходной строки перед совпадением. "abc".replace(/b/, "$`") → "aac"
$' Часть исходной строки после совпадения. "abc".replace(/b/, "$'") → "acc"

Если объявленная группа не участвовала в совпадении, подставляется пустая строка. Если группы с номером вообще нет, запись вроде $1 остаётся текстом. Строка, возвращаемая функцией замены, не разбирается на подстановки: text.replace(pattern, () => replacement) вставит значение replacement буквально, включая $&.

Как получить результат

Здесь re — объект RegExp, text — строка. Наличие g меняет не только число найденных совпадений, но и форму результата match().

Методы поиска и замены
Задача Вызов Результат
Проверить наличие re.test(text) true или false. Для проверки всей строки добавьте якоря.
Получить совпадение и группы re.exec(text) Массив с группами, index, input и при наличии именованных групп — groups; при неудаче null.
Найти первое или все совпадения text.match(re) Без g — совпадение с группами, с g — только полные совпадения. При неудаче null.
Перебрать совпадения с группами text.matchAll(re) Итератор результатов. Объект RegExp должен иметь g, иначе TypeError. Исходный lastIndex не меняется, но его начальное значение учитывается.
Узнать позицию text.search(re) Индекс начала или -1. Поиск начинается с нулевой позиции; g не нужен.
Заменить текст text.replace(re, replacement) Новая строка. Без g заменяется первое совпадение, с g — все.
Разделить строку text.split(re) Массив частей; g не требуется. Захватывающие группы разделителя тоже попадают в массив.

Частые ошибки

  • [A-z] — не только латинские буквы: диапазон захватывает символы между Z и a, включая [ и _. Для букв используйте [A-Za-z].
  • \d* допускает пустое совпадение. Для хотя бы одной цифры нужен \d+, для строки только из цифр — ^\d+$.
  • ? после элемента делает его необязательным, после квантификатора — меняет жадность, после ( — начинает специальную конструкцию группы или проверки.
  • Без u или v некоторые ошибочные экранирования могут восприниматься как обычные символы. Эти режимы помогают обнаружить ошибку, но не заменяют проверку смысла шаблона.
  • Синтаксис движков различается. Не переносите из PCRE флаг x, атомарные группы (?>…) и притяжательные квантификаторы *+ как готовый JavaScript.
  • Вложенные повторения вроде (a+)+$ могут вызывать долгий перебор на строках без совпадения. Убирайте неоднозначные повторения и ограничивайте длину входа; ленивый квантификатор сам по себе проблему не решает.
  • Шаблон \d{4}-\d{2}-\d{2} проверяет форму даты, но не существование дня в календаре. Для разбора HTML используйте DOM-парсер, для URL — URL с отдельной проверкой допустимого протокола и других условий задачи.

Поддержка синтаксиса

Поддержка объекта RegExp не означает поддержку всех записей. Проверяйте отдельно ретроспективные проверки, флаг v, локальные модификаторы и RegExp.escape(). Неизвестный движку синтаксис в литерале не позволит разобрать скрипт. Возможность проверяют через конструктор:

JavaScript
let supportsUnicodeSets = false;
try {
  new RegExp("[a--b]", "v");
  supportsUnicodeSets = true;
} catch {
  // В этой среде нужен шаблон без возможностей режима v.
}
console.log(supportsUnicodeSets);

Записи \A, \z и \Z для границ всего входного текста пока относятся к экспериментальному расширению. Не используйте их как переносимую замену ^ и $ без m.

Поддержка браузерами

Поддерживается во всех основных браузерах с 2015 года.

Отдельные значения (1) поддерживаются не везде — смотрите таблицу.

Показать браузеры
Совместимостьjavascript.regular_expressions
ВозможностьКомпьютерыМобильные
ChromeEdgeFirefoxOperaSafariChrome AndroidFirefox for AndroidOpera AndroidSafari on iOSSamsung BrowserWebView AndroidWebView on iOS
backreference✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Buffer boundary assertion: \A, \z, \Z××◐preview×✓preview×××××××
Capturing group: (...)✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Character class: [...], [^...]✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Character class escape: \d, \D, \w, \W, \s, \S✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Character escape: \n, \x✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
unicode✓50✓12✓46✓37✓10✓50✓46✓37✓10✓5.0✓50✓10
disjunction✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Input boundary assertion: ^, $✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Literal character: a, b✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Lookahead assertion: (?=...), (?!...)✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Lookbehind assertion: (?&lt;=...), (?&lt;!...)✓62✓79✓78✓49✓16.4✓62✓79✓46✓16.4✓8.0✓62✓16.4
modifier✓125✓125✓132✓111✓26✓125✓132✓83✓26✓27.0✓125✓26
Named backreference: \k&lt;name&gt;✓64✓79✓78✓51✓11.1✓64✓79✓47✓11.3✓9.0✓64✓11.3
Named capture group: (?&lt;name&gt;...)✓64✓79✓78✓51✓11.1✓64✓79✓47✓11.3✓9.0✓64✓11.3
Duplicate names in different disjunction alternatives are allowed✓125✓125✓129✓111✓17✓125✓129✓83✓17✓27.0✓125✓17
Non-capturing group: (?:...)✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
quantifier✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Unicode character class escape: \p{...}, \P{...}✓64✓79✓78✓51✓11.1✓64✓79✓47✓11.3✓9.0✓64✓11.3
wildcard✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1
Word boundary assertion: \b, \B✓1✓12✓1✓5✓1✓18✓4✓10.1✓1✓1.0✓4.4✓1

поддерживаетсяс ограниченияминет поддержкинет данных≤31 — не позднее версии 31

Примеры для запуска

Именованные группы и замена

Шаблон извлекает серию и номер заказа, а строка замены переставляет их. Замените входной код на TK-12: проверьте, как обрабатывается отсутствие совпадения.

index.html
HTML
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Именованные группы и замена</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>
main.js
JavaScript
const pattern = /(?<series>[A-Z]{2})-(?<number>\d{3})/
const text = 'Заказ TK-101 готов'
const match = text.match(pattern)

if (match) {
    console.log(match.groups.series, match.groups.number)
} else {
    console.log('Код заказа не найден')
}

console.log(text.replace(pattern, '$<number> ($<series>)'))

Кириллица: \w и \p{L}

Первый поиск находит только tea, второй — ещё чай и мёд. Добавьте слово на другой письменности и сравните результаты. Квантификатор {3,} оставляет последовательности не короче трёх символов.

index.html
HTML
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Кириллица: \w и \p{L}</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>
main.js
JavaScript
const text = "tea, чай и мёд";

console.log(text.match(/\w{3,}/g));
console.log(text.match(/\p{L}{3,}/gu));

Жадность и проверки

Сравните поиск фрагментов в кавычках с .+ и .+?. Затем проверьте, как (?=₽) извлекает цены, не включая знак рубля и количество товара.

index.html
HTML
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Жадность и проверки</title>
</head>
<body>
  <script src="main.js"></script>
</body>
</html>
main.js
JavaScript
const text = '"чай" и "мёд"'
console.log(text.match(/".+"/g))
console.log(text.match(/".+?"/g))

const prices = 'чай 120₽, мёд 250₽, 2 шт.'
console.log(prices.match(/\d+(?=₽)/g))

Спецификация

ECMAScript: грамматика и выполнение регулярных выражений.