Lookahead и lookbehind

Механизмы lookahead и lookbehind пришли из регулярных выражений и используются для проверки контекста символов без включения этого контекста в итоговое совпадение. В библиотеке Inputmask эти конструкции особенно полезны при создании сложных динамических масок, где поведение ввода зависит от соседних символов, положения курсора или уже введённых данных.

Inputmask поддерживает регулярные выражения внутри масок через режим regex, а также использует внутреннюю логику проверки шаблонов, где lookahead/lookbehind позволяют строить интеллектуальные ограничения.


Позитивный lookahead

Позитивный lookahead проверяет, что после текущей позиции присутствует определённая последовательность.

Синтаксис:

(?=pattern)

Конструкция означает:

«совпадение допустимо только если дальше идёт pattern»

При этом сам pattern не включается в результат.


Простейший пример

Inputmask({
    regex: "\\d(?=\\d{3})"
}).mask("#test");

Разбор:

Конструкция Значение
\d цифра
(?=\d{3}) после неё должны идти ещё три цифры

Подойдут:

1234
5678

Не подойдут:

12
123

Проверка окончания строки

Lookahead часто используется для проверки конца ввода.

Пример:

Inputmask({
    regex: "\\d+(?= USD)"
}).mask("#currency");

Подходит:

100 USD
250 USD

Не подходит:

100 EUR
100US D

Здесь:

(?= USD)

требует наличие строки " USD" после числа.


Проверка формата телефонного номера

Inputmask({
    regex: "\\+7(?=\\s\\(\\d{3}\\))"
}).mask("#phone");

Проверяется:

  • наличие +7

  • далее обязательно:

    • пробел
    • код в скобках

Подходящий ввод:

+7 (777)

Неподходящий:

+7123
+7-777

Позитивный lookahead внутри сложной маски

Inputmask({
    regex: "[A-Z]{2}(?=\\d{4})\\d{4}"
}).mask("#code");

Структура:

Фрагмент Значение
[A-Z]{2} две буквы
(?=\d{4}) дальше должны идти четыре цифры
\d{4} сами цифры

Подходящие значения:

AB1234
ZX9999

Негативный lookahead

Негативный lookahead проверяет отсутствие шаблона после текущей позиции.

Синтаксис:

(?!pattern)

Означает:

«дальше НЕ должно быть pattern»


Запрет определённых последовательностей

Inputmask({
    regex: "^(?!000)\\d{3}$"
}).mask("#code");

Разбор:

Конструкция Значение
^ начало строки
(?!000) строка не должна начинаться с 000
\d{3} три цифры

Допустимо:

123
555
901

Недопустимо:

000

Исключение специальных префиксов

Inputmask({
    regex: "^(?!admin).*"
}).mask("#login");

Запрещён ввод:

admin
administrator
admin123

Разрешён:

user
manager
root

Проверка доменных имён

Inputmask({
    regex: "^(?!.*\\.exe$).+"
}).mask("#filename");

Здесь:

(?!.*\.exe$)

означает:

строка не должна заканчиваться на .exe

Подходит:

photo.png
archive.zip
notes.txt

Не подходит:

virus.exe
setup.exe

Позитивный lookbehind

Lookbehind проверяет наличие шаблона перед текущей позицией.

Синтаксис:

(?<=pattern)

Означает:

«перед текущей позицией должен быть pattern»


Проверка символов после префикса

Inputmask({
    regex: "(?<=USD)\\d+"
}).mask("#money");

Подходит:

USD100
USD250

Не подходит:

EUR100
RUB500

Здесь число допустимо только если перед ним находится "USD".


Lookbehind в масках идентификаторов

Inputmask({
    regex: "(?<=ID-)\\d{5}"
}).mask("#id");

Разрешено:

ID-12345
ID-99999

Запрещено:

12345
CODE-12345

Проверка даты после разделителя

Inputmask({
    regex: "(?<=:)\\d{2}"
}).mask("#time");

Допускается:

12:30
08:45

После двоеточия обязательно две цифры.


Негативный lookbehind

Негативный lookbehind проверяет отсутствие шаблона перед текущей позицией.

Синтаксис:

(?<!pattern)

Означает:

«перед текущей позицией НЕ должен находиться pattern»


Исключение нежелательных префиксов

Inputmask({
    regex: "(?<!-)\\d+"
}).mask("#number");

Не допускается число после минуса.

Подходит:

123
555

Не подходит:

-123

Защита от двойных разделителей

Inputmask({
    regex: "(?<!__)\\w+"
}).mask("#username");

Исключаются последовательности с двойным подчёркиванием перед словом.


Использование lookaround в email-масках

Lookahead и lookbehind вместе называют lookaround-конструкциями.

Пример:

Inputmask({
    regex: "^(?!.*\\.\\.)[A-Za-z0-9._%+-]+(?<!\\.)@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
}).mask("#email");

Разбор:

Конструкция Назначение
(?!.*\.\.) запрет двойных точек
(?<!\.) запрет точки перед @
@[A-Za-z0-9.-]+ доменная часть
\.[A-Za-z]{2,} доменная зона

Допустимо:

user@mail.com
test.user@gmail.com

Недопустимо:

user..name@mail.com
user.@mail.com

Комбинирование lookahead и lookbehind

Inputmask({
    regex: "(?<=\\[)\\d+(?=\\])"
}).mask("#array");

Проверяется число внутри квадратных скобок.

Подходит:

[123]
[999]

Не подходит:

123
(123)

Проверка пароля

Inputmask({
    regex: "^(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*]).{8,}$"
}).mask("#password");

Используются несколько lookahead одновременно.

Условия:

Проверка Конструкция
хотя бы одна заглавная буква (?=.*[A-Z])
хотя бы одна цифра (?=.*\d)
хотя бы один спецсимвол (?=.*[!@#$%^&*])
минимум 8 символов .{8,}

Подходящие варианты:

Test123!
Qwerty9@

Проверка номера документа

Inputmask({
    regex: "^(?=.{10}$)(?=.*\\d)(?=.*[A-Z])[A-Z0-9]+$"
}).mask("#doc");

Условия:

  • длина ровно 10 символов
  • минимум одна цифра
  • минимум одна буква
  • только латиница и цифры

Lookaround и производительность

Lookahead и lookbehind увеличивают сложность регулярного выражения. Особенно это заметно в Inputmask при:

  • динамическом вводе
  • большом количестве альтернатив
  • множественных проверках
  • длинных строках

Проблемные конструкции:

(?=.*a)(?=.*b)(?=.*c)(?=.*d)

или:

(?!.*forbidden)

на очень длинных строках.


Ограничения lookbehind

Не все JavaScript-движки одинаково поддерживают lookbehind.

Поддержка появилась относительно поздно:

Среда Поддержка
Современный Chrome есть
Firefox есть
Safari старых версий проблемы
Internet Explorer отсутствует

Из-за этого некоторые Inputmask-конфигурации с (?<=...) и (?<!...) могут работать нестабильно в старых браузерах.


Безопасная альтернатива lookbehind

Вместо:

(?<=USD)\d+

часто используют:

USD(\d+)

С последующим извлечением группы.

Такой подход:

  • совместим со старыми браузерами
  • быстрее выполняется
  • проще отлаживается

Использование в режиме regex

Lookaround чаще всего применяется именно в режиме regex.

Пример подключения:

Inputmask({
    regex: "^(?=.*[A-Z])(?=.*\\d).+$"
}).mask("#field");

Inputmask начинает интерпретировать всю маску как полноценное регулярное выражение.


Отличие lookaround от групп захвата

Lookaround:

  • не возвращает совпадение
  • не сохраняет данные
  • только проверяет условие

Группа захвата:

(...)

сохраняет найденный фрагмент.

Пример:

USD(\d+)

Здесь:

(\d+)

будет доступно как группа.

В lookahead:

(?=\d+)

данные не сохраняются.


Практический пример сложной маски

Inputmask({
    regex: "^(?=.*[A-Z])(?=.*\\d)(?!.*\\s)(?=.*[_-]).{6,20}$"
}).mask("#login");

Требования:

Проверка Значение
(?=.*[A-Z]) минимум одна заглавная
(?=.*\d) минимум одна цифра
(?!.*\s) запрет пробелов
(?=.*[_-]) наличие _ или -
.{6,20} длина от 6 до 20

Подходит:

Admin_1
User-99

Не подходит:

admin
test test
USERONLY

Lookaround в динамических масках

Inputmask позволяет комбинировать регулярные выражения с динамическими правилами:

Inputmask({
    regex: "^(?:(?=\\d{10}$)\\d+|(?=\\d{12}$)\\d+)$"
}).mask("#number");

Разрешаются:

  • 10 цифр
  • 12 цифр

Другие длины запрещены.


Типичные ошибки

Неправильное экранирование

Ошибка:

regex: "\d+(?=\w)"

Правильно:

regex: "\\d+(?=\\w)"

В строках JavaScript обратный слэш нужно экранировать.


Использование lookbehind в старых браузерах

(?<=USD)\d+

может не работать в Internet Explorer.


Слишком сложные проверки

Конструкция:

^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@#$%^&+=]).{8,}$

при непрерывном вводе может вызывать заметные задержки.


Рекомендации по проектированию

Использование lookahead для:

  • проверки длины
  • проверки обязательных символов
  • проверки формата
  • исключения запрещённых шаблонов

Использование lookbehind для:

  • проверки префиксов
  • анализа предыдущего контекста
  • обработки специальных разделителей

Избегание чрезмерной вложенности

Плохо:

(?=(?!(?=.*abc)).*)

Лучше:

(?!.*abc)

Разделение сложной логики

Вместо гигантского regex:

Inputmask({
    regex: "..."
})

часть проверок лучше переносить:

  • в onBeforePaste
  • в validator
  • в пользовательские функции
  • в post-validation

Это повышает:

  • читаемость
  • производительность
  • удобство поддержки