Perché un RegExp con la bandiera globale fornisce risultati errati?

Qual è il problema con questa espressione regolare quando utilizzo il flag globale e il flag senza distinzione tra maiuscole e minuscole? Query è un input generato dall’utente. Il risultato dovrebbe essere [vero, vero].

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] 

L’object RegExp tiene traccia lastIndex cui si è verificata una corrispondenza, quindi nelle corrispondenze successive inizierà dall’ultimo indice utilizzato, anziché da 0. Dai uno sguardo:

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

Se non desideri reimpostare manualmente lastIndex su 0 dopo ogni test, rimuovi semplicemente il flag g .

Ecco l’algoritmo dettato dalle specifiche (sezione 15.10.6.2):

RegExp.prototype.exec (stringa)

Esegue una corrispondenza di espressioni regolari di stringa con l’espressione regolare e restituisce un object Array contenente i risultati della corrispondenza, oppure null se la stringa non corrisponde La stringa ToString (stringa) viene ricercata per un’occorrenza del modello di espressione regolare come segue:

  1. Sia S il valore di ToString (stringa).
  2. Sia la lunghezza sia la lunghezza di S.
  3. Lascia che lastIndex sia il valore della proprietà lastIndex.
  4. Lascia che sia il valore di ToInteger (lastIndex).
  5. Se la proprietà globale è falsa, lascia i = 0.
  6. Se I <0 o I> length, impostare lastIndex su 0 e restituire null.
  7. Chiama [[Match]], dandogli gli argomenti S e i. Se [[Corrispondenza]] ha restituito un errore, andare al passaggio 8; altrimenti lascia che sia il suo risultato di stato e vai al punto 10.
  8. Sia i = i + 1.
  9. Vai al passaggio 6.
  10. Lascia che sia il valore endIndex di r.
  11. Se la proprietà globale è vera, imposta lastIndex su e.
  12. Sia n la lunghezza della matrice di cattura di r. (Questo è lo stesso valore di NCapturingParens di 15.10.2.1).
  13. Restituisce un nuovo array con le seguenti proprietà:
    • La proprietà index è impostata sulla posizione della sottostringa corrispondente all’interno della stringa completa S.
    • La proprietà di input è impostata su S.
    • La proprietà length è impostata su n + 1.
    • La proprietà 0 è impostata sulla sottostringa corrispondente (ovvero la porzione di S tra offset i inclusive e offset e exclusive).
    • Per ogni intero i tale che I> 0 e I ≤ n, impostare la proprietà denominata ToString (i) sull’elemento ith dell’array di cattura di r.

Si sta utilizzando un singolo object RegExp e l’esecuzione più volte. Ad ogni successiva esecuzione continua dall’ultimo indice di corrispondenza.

È necessario “ripristinare” la regex per iniziare dall’inizio prima di ogni esecuzione:

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

Detto questo, può essere più leggibile creare ogni volta un nuovo object RegExp (l’overhead è minimo in quanto RegExp è comunque memorizzato nella cache):

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

RegExp.prototype.test aggiorna la proprietà lastIndex delle espressioni regolari in modo che ogni test inizi dove si è fermato l’ultimo. Suggerirei di utilizzare String.prototype.match poiché non aggiorna la proprietà lastIndex :

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

Nota: !! lo converte in un booleano e quindi inverte il valore booleano in modo che rifletta il risultato.

In alternativa, è sufficiente ripristinare la proprietà lastIndex :

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

La rimozione di g flag globale risolverà il tuo problema.

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

Dovrebbe essere

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

Usando la flag / g dice di continuare a cercare dopo un colpo.

Se la corrispondenza ha esito positivo, il metodo exec () restituisce una matrice e aggiorna le proprietà dell’object di espressione regolare.

Prima della tua prima ricerca:

 myRegex.lastIndex //is 0 

Dopo la prima ricerca

 myRegex.lastIndex //is 8 

Rimuovi g e esce dalla ricerca dopo ogni chiamata a exec ().

Ho avuto la funzione:

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

La prima chiamata funziona. La seconda chiamata no. L’operazione slice lamenta di un valore nullo. Presumo che questo sia dovuto a re.lastIndex . Questo è strano perché mi aspetterei che venga assegnato un nuovo RegExp ogni volta che viene chiamata la funzione e non condivisa tra più invocazioni della mia funzione.

Quando l’ho cambiato in:

 var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g'); 

Quindi non ottengo l’ lastIndex effetto lastIndex . Funziona come me lo aspetterei.