Неочевидная особенность регулярных выражений в JavaScript

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

Рассмотрим пример:

const digits = /\d+/;

digits.test("123");
// -> true

digits.test("456");
// -> true

digits.test("789");
// -> true

Всё работает ожидаемо. А теперь добавим флаг g, с которым регулярка должна искать все совпадения:

const digits = /\d+/g;

digits.test("123");
// -> true

digits.test("456");
// -> false

digits.test("789");
// -> true

Теперь проверка срабатывает через раз. Дело в том, что флаг g включает сохранение состояния поиска в поле lastIndex, регулярка становится stateful, а не stateless. При вызове методов test или exec поиск начинается с позиции lastIndex. При отсутствии совпадений lastIndex обнуляется, как президентские сроки, а при обнаружении совпадения в lastIndex записывается позиция после совпадения.

Чтобы избежать этой проблемы, lastIndex можно перезаписывать вручную:

const digits = /\d+/g;

digits.test("123");
// -> true

digits.lastIndex = 0;
digits.test("456");
// -> true

digits.lastIndex = 0;
digits.test("789");
// -> true

К сожалению, это не самое изящное решение, оно добавляет когнитивной нагрузки — нужно помнить о необходимости сброса индекса при каждом поиске. Можно намекать на природу регулярки префиксом stateful или global в названии переменной, чтобы при использовании не забывали обнулять lastIndex; можно писать и использовать собственные функции-обёртки, которые будут обнулять lastIndex при каждом вызове; а можно продолжать хардкодить регулярки в местах их использования — выбор за вами.