Чому RegExp з глобальним прапором дає неправильні результати?


277

Яка проблема з цим регулярним виразом, коли я використовую глобальний прапор та прапор, нечутливий до регістру? Запит - це введений користувачем вхід. Результат повинен бути [правдивим, правдивим].

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


54
Ласкаво просимо до однієї з багатьох пасток RegExp в JavaScript. Він має один з найгірших інтерфейсів для обробки регулярних виразів, який я коли-небудь зустрічав, повний дивних побічних ефектів і неясних застережень. Більшість найпоширеніших завдань, які ти зазвичай хочеш виконати з регулярним виразом, важко правильно написати.
bobince

XRegExp виглядає як хороша альтернатива. xregexp.com
близько

Див відповідь тут , а також: stackoverflow.com/questions/604860 / ...
Prestaul

Одне з варіантів, якщо ви можете відмовитися від цього, - це використовувати прямий прямокутник, а не зберігати його re.
thoan

Відповіді:


350

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 після кожного тесту, просто зніміть gпрапор.

Ось алгоритм, який диктують характеристики (розділ 15.10.6.2):

RegExp.prototype.exec (рядок)

Виконує збіг регулярного вираження рядка проти регулярного виразу і повертає об’єкт масиву, що містить результати відповідності, або null, якщо рядок не збігається. Шукає рядок ToString (рядок) для появи шаблону регулярного вираження таким чином:

  1. Нехай S - значення ToString (рядок).
  2. Нехай довжина буде довжиною S.
  3. Нехай lastIndex є значенням властивості lastIndex.
  4. Нехай я - значення ToInteger (lastIndex).
  5. Якщо глобальна властивість хибна, нехай i = 0.
  6. Якщо довжина I <0 або I>, то встановіть lastIndex на 0 і поверніть null.
  7. Виклик [[Матч]], надавши йому аргументи S і i. Якщо [[Матч]] повернув помилку, перейдіть до кроку 8; інакше нехай r є його державним результатом і переходимо до кроку 10.
  8. Нехай i = i + 1.
  9. Перейдіть до кроку 6.
  10. Нехай це значення r endIndex.
  11. Якщо глобальна властивість вірна, встановіть lastIndex на e.
  12. Нехай n - довжина масиву r захоплення. (Це те саме значення, що і NCapturingParens 15.10.2.1.)
  13. Повернути новий масив із такими властивостями:
    • Властивість індексу встановлюється на позицію відповідного підрядка в повному рядку S.
    • Властивість вводу встановлено на S.
    • Властивість довжини встановлено на n + 1.
    • Властивість 0 встановлюється на підстрокову підстроку (тобто частину S між зміщенням i включно та зміщенням e виключно).
    • Для кожного цілого числа i такого, що I> 0 і I ≤ n, встановіть властивість під назвою ToString (i) на i-й елемент масиву r's captures.

83
Це подібно до посібника з автостопом до дизайну Galaxy API тут. "Той підводний камінь, в який ви потрапили, був ідеально задокументований у специфікації протягом декількох років, якщо ви тільки
намагалися

5
Клейкий прапор Firefox зовсім не робить те, що ви маєте на увазі. Швидше, він діє так, як ніби на початку регулярного виразу був ^, ПІСЛЯ того, що це ^ відповідає поточній позиції рядка (lastIndex), а не початку рядка. Ви ефективно випробовуєте, чи відповідає регулярний вираз «прямо тут» замість «будь-де після lastIndex». Дивіться посилання, яке ви надали!
Зробити

1
Вступне твердження цієї відповіді просто не точне. Ви виділили крок 3 специфікації, яка нічого не говорить. Фактичний вплив дії lastIndexполягає в кроках 5, 6 та 11. Ваше вступне слово є вірним лише в тому випадку, якщо ГЛОБАЛЬНА ЗАПИТАННЯ Встановлена.
Prestaul

@Prestaul так, ви праві, що він не згадує глобальний прапор. Це було, мабуть, (не пам'ятаю, що я тоді думав) неявним через спосіб постановки питання. Не соромтесь відредагувати відповідь або видалити її та посилання на свою відповідь. Також дозвольте запевнити вас, що ви кращі за мене. Насолоджуйтесь!
Ionuț G. Stan

@ IonuțG.Stan, вибач, якщо мій попередній коментар здався нападоподібним, це був не мій намір. Я не можу редагувати це на даний момент, але я не намагався кричати, аби звернути увагу на суть мого коментаря. Моє ліжко!
Prestaul

72

Ви використовуєте один 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));

1
Або просто не використовуйте gпрапор.
Melpomene

36

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

11

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

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

Має бути

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

0

Потрібно встановити re.lastIndex = 0, тому що, якщо g flag regex відстежує останній збіг, тому тест не буде переходити на тестування тієї ж строки, для цього вам потрібно зробити re.lastIndex = 0

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

console.log(result)


-1

Використання прапора / g повідомляє йому продовжувати пошук після попадання.

Якщо збіг вдається, метод exec () повертає масив і оновлює властивості об'єкта регулярного вираження.

Перед першим пошуком:

myRegex.lastIndex
//is 0

Після першого пошуку

myRegex.lastIndex
//is 8

Видаліть g, і він завершує пошук після кожного дзвінка до exec ().


ОП не використовується exec.
Melpomene

-1

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

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ефекту утримування. Це працює так, як я б очікував.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.