Почему Javascript regex.exec() не всегда возвращает одно и то же значение?

В консоли Chrome или Firebug:

reg = /ab/g
str = "abc"
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null

Является ли exec каким-то образом stateful и зависит от того, что он вернул в предыдущий раз? Или это просто ошибка? Я не могу заставить это случиться все время. Например, если "str" выше было "abc abc", этого не происходит.

Ответ 1

JavaScript-объект RegExp состоянием.

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

Если совпадений больше не найдено, индекс автоматически сбрасывается в 0.


Чтобы сбросить его вручную, установите свойство lastIndex.

reg.lastIndex = 0;

Это может быть очень полезной функцией. Вы можете начать оценку в любой точке строки, если хотите, или, если в цикле, вы можете остановить ее после желаемого количества совпадений.


Здесь демонстрация типичного подхода к использованию регулярных выражений в цикле. Он использует тот факт, что exec возвращает null когда больше нет совпадений, выполняя присваивание в качестве условия цикла.

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

while (match = re.exec(str))
    results.push(+match[1]);

ДЕМО: http://jsfiddle.net/pPW8Y/


Если вам не нравится размещение назначения, цикл может быть переработан, например, так...

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

do {
    match = re.exec(str);
    if (match)
        results.push(+match[1]);
} while (match);

ДЕМО: http://jsfiddle.net/pPW8Y/1/

Ответ 2

Из Документы MDN:

Если ваше регулярное выражение использует флаг "g", вы можете использовать метод exec несколько раз, чтобы найти последовательные совпадения в одной строке. Когда вы это сделаете, поиск начинается с подстроки str, заданной с помощью свойства регулярного выражения lastIndex (тест также увеличивает свойство lastIndex).

Поскольку вы используете флаг g, exec продолжается от последней строки с совпадением до тех пор, пока не дойдет до конца (возвращает null), затем начинается.


Лично я предпочитаю идти в обратном направлении с помощью str.match(reg)

Ответ 3

Несколько совпадений

Если вашему регулярному выражению нужен флаг g (глобальное совпадение), вам нужно будет reset индекс (позиция последнего совпадения) с помощью свойства lastIndex.

reg.lastIndex = 0;

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

Если ваше регулярное выражение использует флаг "g", вы можете использовать exec метод несколько раз, чтобы найти последовательные совпадения в одной строке. Когда вы это сделаете, поиск начинается с подстроки str, указанной в свойство lastIndex регулярного выражения (тест также будет продвигаться вперед свойство lastIndex)

Одиночный матч

Если существует только одно возможное совпадение, вы можете просто переписать ваше регулярное выражение, опуская флаг g, так как индекс