javascript - De ce un RegExp cu steagul global oferă rezultate greșite?


Care este problema cu această expresie regulată când folosesc flag-ul global și flag-ul insensibil la majuscule? Interogarea este o intrare generată de utilizator. Rezultatul ar trebui să fie [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"));

Răspuns #1

Un obiect RegExp cu flag-ul g păstrează evidența lastIndex unde a avut loc o potrivire, așa că la potrivirile ulterioare va începe de la ultimul index folosit, în loc de 0. Aruncă o privire:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
console.log(re.lastIndex);

console.log(re.test('Foo Bar'));
console.log(re.lastIndex);

console.log(re.test('Foo Bar'));
console.log(re.lastIndex);

Dacă nu vrei să resetezi manual lastIndex la 0 după fiecare test, pur și simplu elimină flagul g.

Iată algoritmul pe care specificațiile îl impun (secțiunea 15.10.6.2):

RegExp.prototype.exec(string)

Efectuează o potrivire a expresiei regulate a șirului împotriva expresiei regulate și returnează un obiect Array care conține rezultatele potrivirii sau null dacă șirul nu s-a potrivit. Șirul ToString(string) este căutat pentru o apariție a modelului expresiei regulate după cum urmează:

  1. Fie R acest obiect RexExp.
  2. Fie S valoarea lui ToString(string).
  3. Fie length lungimea lui S.
  4. Fie lastIndex valoarea proprietății lastIndex din R.
  5. Fie i valoarea lui ToInteger(lastIndex).
  6. Dacă proprietatea globală este falsă, să fie i=0.
  7. Dacă i < 0 sau i > lungime atunci setează proprietatea lastIndex a R la 0 și returnează null.
  8. Apelează [[Match]], oferindu-i argumentele S și i. Dacă [[Match]] a returnat eșec, mergi la pasul 9; altfel, lasă ca r să fie rezultatul său State și mergi la pasul 10.
  9. Fie i=i+1.
  10. Mergeți la pasul 7.
  11. Fie e valoarea endIndex a lui r.
  12. Dacă proprietatea globală este adevărată, setează proprietatea lastIndex a lui R la e.
  13. Fie n lungimea matricei de capturi a lui r. (Aceasta este aceeași valoare ca NCapturingParens din 15.10.2.1.)
  14. Returnează un nou tablou cu următoarele proprietăți:
  • Proprietatea index este setată la poziția subșirului găsit în cadrul șirului complet S.
  • Proprietatea de intrare este setată la S.
  • Proprietatea length este setată la n + 1.
  • Proprietatea 0 este setată la subșirul corespunzător (adică porțiunea din S între decalajul i inclusiv și decalajul e exclusiv).
  • Pentru fiecare număr întreg i astfel încât i > 0 și i ≤ n, setează proprietatea numită ToString(i) la al i-lea element din tabloul de capturi al lui r.

Răspunsul #2

Folosești un singur obiect RegExp și îl execuți de mai multe ori. La fiecare execuție succesivă continuă de la ultimul indice de potrivire.

Trebuie să „resetați” expresia regulată pentru a începe de la început înainte de fiecare execuție:

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

Spunând asta, poate fi mai ușor de citit să creezi un nou obiect RegExp de fiecare dată (suprasarcina este minimă deoarece RegExp este oricum memorat în cache):

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

Răspunsul #3

RegExp.prototype.test actualizează proprietatea lastIndex a expresiilor regulate astfel încât fiecare test să înceapă de unde s-a oprit ultimul. Aș sugera utilizarea String.prototype.match deoarece nu actualizează proprietatea lastIndex:

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

Notă: !! îl convertește într-un boolean și apoi inversează booleanul astfel încât să reflecte rezultatul.

Alternativ, ai putea pur și simplu să resetezi proprietatea lastIndex:

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

Răspunsul #4

Îndepărtarea flag-ului global g va rezolva problema ta.

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

Ar trebui să fie

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