Spec-Zone.ru › JavaScript

Утверждение просмотра вперед: (?=...), (?!...)

Базовый уровень Широко доступно

Эта возможность хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна во всех браузерах с июля 2015 года.

Утверждение просмотра вперед («lookahead assertion») «смотрит вперед»: оно пытается сопоставить последующий ввод с заданным шаблоном, но не потребляет часть ввода — если сопоставление успешно, текущая позиция во вводе остается прежней.

Синтаксис

(?=pattern)
(?!pattern)

Параметры

pattern
Шаблон, состоящий из всего, что вы можете использовать в регулярном выражении, включая дизъюнкцию.

Описание

Регулярное выражение обычно сопоставляется слева направо. Вот почему утверждения просмотра вперед (lookahead) и просмотра назад (lookbehind) так называются — lookahead утверждает то, что находится справа, а lookbehind утверждает то, что находится слева.

Для того чтобы утверждение (?=pattern) было успешным, pattern должен совпадать с текстом после текущей позиции, но сама текущая позиция не изменяется. Форма (?!pattern) отрицает утверждение — оно успешно, если pattern не совпадает с текущей позицией.

pattern может содержать захватывающие группы. См. страницу захватывающих групп для получения дополнительной информации о поведении в этом случае.

В отличие от других операторов регулярных выражений, в утверждение просмотра вперед нет возврата (backtracking) — это поведение унаследовано от Perl. Это важно только в том случае, когда pattern содержит захватывающие группы, а шаблон, следующий за просмотром вперед, содержит обратные ссылки на эти захваты. Например:

/(?=(a+))a*b\1/.exec("baabac"); // ['aba', 'a']
// Not ['aaba', 'a']

Сопоставление вышеприведенного шаблона происходит следующим образом:

  1. Просмотр вперед (a+) успешен перед первым "a" в "baabac", и "aa" захватывается, потому что квантификатор является жадным.
  2. a*b совпадает с "aab" в "baabac", потому что просмотры вперед не потребляют сопоставленные ими строки.
  3. \1 не совпадает со следующей строкой, потому что для этого требуется два "a", но доступен только один. Таким образом, механизм сопоставления выполняет возврат (backtrack), но он не заходит внутрь просмотра вперед, поэтому захватывающая группа не может быть уменьшена до одного "a", и все сопоставление терпит неудачу в этот момент.
  4. exec() повторно пытается выполнить сопоставление в следующей позиции — перед вторым "a". На этот раз просмотр вперед совпадает с "a", и a*b совпадает с "ab". Обратная ссылка \1 совпадает с захваченным "a", и сопоставление завершается успешно.

Если бы регулярное выражение могло выполнить возврат внутрь просмотра вперед и пересмотреть сделанный там выбор, то сопоставление было бы успешным на шаге 3, где (a+) сопоставил бы первый "a" (вместо первых двух "a") и a*b сопоставил бы "aab", даже не предпринимая повторной попытки в следующей позиции ввода.

Отрицательные просмотры вперед также могут содержать захватывающие группы, но обратные ссылки имеют смысл только внутри pattern, потому что, если сопоставление продолжается, pattern обязательно будет не сопоставлено (иначе утверждение терпит неудачу). Это означает, что вне pattern, обратные ссылки на эти захватывающие группы в отрицательных просмотрах вперед всегда успешны. Например:

/(.*?)a(?!(a+)b\2c)\2(.*)/.exec("baaabaac"); // ['baaabaac', 'ba', undefined, 'abaac']

Сопоставление вышеприведенного шаблона происходит следующим образом:

  1. Шаблон (.*?) является нежадным, поэтому он начинает с сопоставления ничего. Однако следующий символ — a, который не совпадает с "b" во вводе.
  2. Шаблон (.*?) совпадает с "b", так что a в шаблоне совпадает с первым "a" в "baaabaac".
  3. В этой позиции просмотр вперед успешно сопоставляется, потому что если (a+) совпадает с "aa", то (a+)b\2c совпадает с "aabaac". Это приводит к неудаче утверждения, поэтому механизм сопоставления выполняет возврат.
  4. Шаблон (.*?) совпадает с "ba", так что a в шаблоне совпадает со вторым "a" в "baaabaac".
  5. В этой позиции просмотр вперед не сопоставляется, потому что оставшаяся часть ввода не соответствует шаблону «любое количество "a", один "b", то же количество "a", один c». Это приводит к успеху утверждения.
  6. Однако, поскольку внутри утверждения ничего не было сопоставлено, обратная ссылка \2 не имеет значения, поэтому она сопоставляется с пустой строкой. Это приводит к тому, что остальная часть ввода потребляется (.*) в конце.

Обычно утверждения не могут быть квантифицированы. Однако в режиме без поддержки Юникода утверждения просмотра вперед могут быть квантифицированы. Это устаревший синтаксис для веб-совместимости, и вам не следует на него полагаться.

/(?=a)?b/.test("b"); // true; the lookahead is matched 0 time

Примеры

Сопоставление строк без их потребления

Иногда полезно проверить, что за сопоставленной строкой следует что-то, не возвращая это в качестве результата. Следующий пример сопоставляет строку, за которой следует запятая/точка, но знаки препинания не включаются в результат:

function getFirstSubsentence(str) {
  return /^.*?(?=[,.])/.exec(str)?.[0];
}

getFirstSubsentence("Hello, world!"); // "Hello"
getFirstSubsentence("Thank you."); // "Thank you"

Аналогичный эффект может быть достигнут путем захвата нужного подсовпадения.

Вычитание и пересечение шаблонов

Используя просмотр вперед, вы можете сопоставить строку несколько раз с разными шаблонами, что позволяет выражать сложные отношения, такие как вычитание (это X, но не Y) и пересечение (это и X, и Y).

Следующий пример сопоставляет любой идентификатор, который не является зарезервированным словом (здесь для краткости показаны только три зарезервированных слова; больше зарезервированных слов можно добавить в эту дизъюнкцию). Синтаксис [$_\p{ID_Start}][$\p{ID_Continue}]* точно описывает набор строк-идентификаторов в спецификации языка; вы можете прочитать больше об идентификаторах в лексической грамматике и об экранировании \p в экранировании класса символов Юникода.

function isValidIdentifierName(str) {
  const re = /^(?!(?:break|case|catch)$)[$_\p{ID_Start}][$\p{ID_Continue}]*$/u;
  return re.test(str);
}

isValidIdentifierName("break"); // false
isValidIdentifierName("foo"); // true
isValidIdentifierName("cases"); // true

Следующий пример сопоставляет строку, которая является как ASCII, так и может использоваться как часть идентификатора:

function isASCIIIDPart(char) {
  return /^(?=\p{ASCII}$)\p{ID_Start}$/u.test(char);
}

isASCIIIDPart("a"); // true
isASCIIIDPart("α"); // false
isASCIIIDPart(":"); // false

Если вы выполняете пересечение и вычитание с конечным числом символов, вы можете захотеть использовать синтаксис пересечения наборов символов, включенный с помощью флага v.

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

Спецификация
Спецификация языка ECMAScript® 2027
# prod-Assertion

Совместимость с браузерами

Десктопные Мобильные Серверные
Chrome Edge Firefox Opera Safari Chrome Android Firefox для Android Opera Android Safari в iOS Samsung Internet WebView Android WebView в iOS Bun Deno Node.js
lookahead_assertion
1
12
1
5
1
18
4
10.1
1
1.0
4.4
1
1.0.0
1.0
0.10.0

Смотрите также

  • Руководство по утверждениям (Assertions)
  • Регулярные выражения
  • Утверждение границ ввода: ^, $
  • Утверждение границ слова: \b, \B
  • Утверждение просмотра назад: (?<=...), (?<!...)
  • Захватывающая группа: (...)

© 2005–2025 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookahead_assertion

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API