2009-10-05 2 views
187

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

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"));

+36

Добро пожаловать в один из многих ловушек RegExp в JavaScript. Он имеет один из худших интерфейсов для обработки регулярных выражений, которые я когда-либо встречал, полный странных побочных эффектов и неясных оговорок. Большинство общих задач, которые вы обычно хотите делать с регулярным выражением, трудно правильно записать. – bobince

+0

XRegExp выглядит как хорошая альтернатива. http://xregexp.com/ – about

+0

См. также здесь: http://stackoverflow.com/questions/604860/interesting-test-of-javascript-regexp – Prestaul

ответ

245

Объект RegExp отслеживает lastIndex, где произошло совпадение, так и на последующих матчах он будет стартовать с последнего использованного индекса, вместо 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')); 

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

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

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

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

  1. Пусть S будет значением ToString (строки).
  2. Пусть длина будет длиной S.
  3. Пусть lastIndex будет значением свойства lastIndex.
  4. Позвольте мне быть значением ToInteger (lastIndex).
  5. Если глобальное свойство является ложным, пусть i = 0.
  6. Если I < 0 или I> длина, то установите lastIndex равным 0 и верните значение null.
  7. Позвоните [[Match]], предоставив ему аргументы S и i. Если [[Match]] вернулся к ошибке, перейдите к шагу 8; в противном случае пусть r будет его результатом State и перейдите к шагу 10.
  8. Пусть i = i + 1.
  9. Перейдите к шагу 6. ​​
  10. Пусть e будет значить значение endIndex.
  11. Если глобальное свойство истинно, установите lastIndex в e.
  12. Пусть n - длина массива захватов r. (Это то же самое значение, что и NCapturingParens 15.10.2.1 в.)
  13. возвращает новый массив со следующими свойствами:
    • Индекс свойство устанавливается в положение совпадающей подстроки в пределах полная строка С.
    • свойство вход установлен в положение S.
    • свойство длина установлена ​​в п + 1.
    • 0 Prope rty устанавливается в подстроку под номером (т. часть S между смещением i включительно и смещение e exclusive).
    • Для каждого целого числа i такого, что I> 0 и I ≤ n, задайте свойство с именем ToString (i) равным i-му элементу массива захватов r.
+39

Это похоже на руководство Hitchhiker по дизайну API Galaxy. «Эта ловушка, в которую вы попали, была полностью задокументирована в спецификации в течение нескольких лет, если вы только потрудились проверить» – Retsam

+4

Липкий флаг Firefox не делает то, что вы подразумеваете вообще. Скорее, он действует так, как если бы в начале регулярного выражения существовал ^, ИСКЛЮЧАЕТ, что это соответствует совпадению * текущей позиции строки (lastIndex), а не началу строки. Вы эффективно проверяете, соответствует ли регулярное выражение «прямо здесь» вместо «где-нибудь после lastIndex». См. Ссылку, которую вы указали! – Doin

+0

Вступительное заявление этого ответа просто неточно. Вы выделили шаг 3 спецификации, который ничего не говорит. Фактическое влияние 'lastIndex' находится на шагах 5, 6 и 11. Ваше вступительное утверждение верно только в том случае, если GLOBAL FLAG SET. – Prestaul

59

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

Вам нужно «сбросить» регулярное выражение, чтобы начать с самого начала перед каждым выполнением:

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)); 
32

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

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

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

В качестве альтернативы, можно просто сбросить 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'); 
Смежные вопросы