Почему RegExp с глобальным флагом дает неправильные результаты?

203

В чем проблема с этим регулярным выражением при использовании глобального флага и флага, нечувствительного к регистру? Запрос - это пользовательский ввод. Результат должен быть [true, true].

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));
  • 48
    Добро пожаловать в одну из многих ловушек RegExp в JavaScript. У него один из худших интерфейсов для обработки регулярных выражений, который я когда-либо встречал, полный странных побочных эффектов и неясных предостережений. Большинство общих задач, которые вы обычно хотите выполнять с помощью регулярных выражений, трудно правильно написать.
  • 0
    XRegExp выглядит как хорошая альтернатива. xregexp.com
Показать ещё 2 комментария
Теги:

6 ответов

285
Лучший ответ

Объект RegExp отслеживает lastIndex, где произошло совпадение, поэтому в последующих матчах он будет начинаться с последнего использованного index, а не 0. Посмотрите:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

Если вы не хотите, чтобы вручную reset lastIndex до 0 после каждого теста, просто удалите флаг g.

Здесь алгоритм, определяемый спецификациями (раздел 15.10.6.2): ​​

RegExp.prototype.exec(строка)

Проводит соответствие регулярного выражения строки против регулярного выражения и возвращает объект Array, содержащий результаты матча, или null, если строка не соответствует Строка ToString (строка) выполняется поиск появление регулярного выражения шаблон следующим образом:

  • Пусть S - значение ToString (string).
  • Пусть длина - длина S.
  • Пусть lastIndex - значение свойства lastIndex.
  • Пусть я - значение ToInteger (lastIndex).
  • Если глобальное свойство является ложным, пусть я = 0.
  • Если я < 0 или I > length, то установите lastIndex равным 0 и верните null.
  • Вызовите [[Match]], предоставив ему аргументы S и i. Если [[Match]] возвращенный отказ, перейдите к шагу 8; в противном случае r будет его результатом состояния и перейдите к шагу 10.
  • Пусть я = я + 1.
  • Перейдите к шагу 6. ​​
  • Пусть значение e равно значению endIndex.
  • Если глобальное свойство истинно, установите lastIndex в e.
  • Пусть n - длина массива r-захватов. (Это тоже самое значение как 15.10.2.1 NCapturingParens.)
  • Возвращает новый массив со следующими свойствами:
    • Индекс свойство устанавливается в положение согласованная подстрока в пределах полной строка S.
    • Установлено свойство ввода к S.
    • Свойство length имеет значение n + 1.
    • Свойство 0 установлено на подстрокой (т.е. частью S между смещением я включительно и offset e exclusive).
    • Для каждого целое число я такое, что I > 0 и я ≤ n, установите свойство с именем ToString (i) в i-й элемент массива r захватывает массив.
  • 66
    Это похоже на Руководство Автостопщика по разработке Galaxy API здесь. «Эта ловушка, в которую вы попали, была отлично задокументирована в спецификации в течение нескольких лет, если бы вы только удосужились проверить»
  • 4
    Липкий флаг Firefox не делает то, что вы подразумеваете вообще. Скорее, он действует так, как если бы в начале регулярного выражения был символ ^, ЗА ИСКЛЮЧЕНИЕМ того, что это ^ соответствует текущей позиции строки (lastIndex), а не началу строки. Вы эффективно проверяете, соответствует ли регулярное выражение «прямо здесь» вместо «где-нибудь после lastIndex». Смотрите ссылку, которую вы предоставили!
Показать ещё 3 комментария
64

Вы используете один объект RegExp и выполняете его несколько раз. При каждом последующем выполнении он продолжается от последнего индекса соответствия.

Вам нужно "reset" регулярное выражение начинать с начала перед каждым исполнением:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

Сказав, что может быть более читаемым каждый раз создавать новый объект RegExp (служебные данные минимальны, поскольку RegExp кэшируется в любом случае):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));
34

RegExp.prototype.test обновляет свойство регулярных выражений lastIndex, чтобы каждый тест начинался с остановки последнего. Я бы предложил использовать String.prototype.match, поскольку он не обновляет свойство lastIndex:

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

Примечание. !! преобразует его в логическое, а затем инвертирует логическое значение, чтобы он отражал результат.

В качестве альтернативы вы можете просто reset свойство lastIndex:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
9

Удаление глобального флага g устраняет вашу проблему.

var re = new RegExp(query, 'gi');

Должно быть

var re = new RegExp(query, 'i');
0

У меня была функция:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

Первый звонок работает. Второй звонок - нет. Операция slice жалуется на нулевое значение. Я предполагаю, что это из-за re.lastIndex. Это странно, потому что я ожидал бы, что новый RegExp будет выделяться каждый раз, когда функция вызывается и не разделяется несколькими вызовами моей функции.

Когда я изменил его на:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

Тогда я не получаю эффект lastIndex holdover. Он работает так, как я ожидал.

0

Использование флага /g сообщает ему продолжить поиск после попадания.

Если совпадение выполнено успешно, метод exec() возвращает массив и обновляет свойства объекта регулярного выражения.

Перед первым поиском:

myRegex.lastIndex
//is 0

После первого поиска

myRegex.lastIndex
//is 8

Удалите g и он выйдет из поиска после каждого вызова exec().

Ещё вопросы

Сообщество Overcoder
Наверх
Меню