Чому 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.