Mengapa RegExp dengan bendera global memberikan hasil yang salah?

277

Apa masalah dengan ekspresi reguler ini ketika saya menggunakan flag global dan flag case-insensitive? Kueri adalah input yang dibuat pengguna. Hasilnya harus [benar, benar].

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

tentang
sumber
54
Selamat datang di salah satu dari banyak jebakan RegExp dalam JavaScript. Ini memiliki salah satu antarmuka terburuk untuk pemrosesan regex yang pernah saya temui, penuh efek samping aneh dan peringatan yang tidak jelas. Sebagian besar tugas umum yang biasanya ingin Anda lakukan dengan regex sulit untuk dieja dengan benar.
bobince
XRegExp terlihat seperti alternatif yang baik. xregexp.com
sekitar
Lihat jawaban di sini juga: stackoverflow.com/questions/604860/…
Prestaul
Salah satu solusi, jika Anda bisa melakukannya, adalah menggunakan regex literal secara langsung alih-alih menyimpannya re.
thdoan

Jawaban:

350

The RegExpobjek melacak lastIndexdi mana pertandingan terjadi, sehingga pada pertandingan berikutnya akan mulai dari indeks yang terakhir digunakan, bukan 0. Coba lihat:

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

Jika Anda tidak ingin mengatur ulang secara manual lastIndexke 0 setelah setiap pengujian, hapus saja gflag.

Berikut adalah algoritma yang ditentukan oleh spesifikasi (bagian 15.10.6.2):

RegExp.prototype.exec (string)

Melakukan kecocokan ekspresi string reguler terhadap ekspresi reguler dan mengembalikan objek Array yang berisi hasil kecocokan, atau null jika string tidak cocok dengan string ToString (string) dicari untuk terjadinya pola ekspresi reguler sebagai berikut:

  1. Misalkan S adalah nilai ToString (string).
  2. Biarkan panjang menjadi panjang S.
  3. Biarkan lastIndex menjadi nilai properti lastIndex.
  4. Biarkan saya menjadi nilai ToInteger (lastIndex).
  5. Jika properti global salah, misalkan i = 0.
  6. Jika saya <0 atau I> panjang maka atur lastIndex ke 0 dan kembali nol.
  7. Call [[Match]], berikan argumen S dan i. Jika [[Match]] kembali gagal, lanjutkan ke langkah 8; jika tidak, biarkan r menjadi hasil Negara dan lanjutkan ke langkah 10.
  8. Biarkan i = i +1.
  9. Lanjutkan ke langkah 6.
  10. Biarkan e menjadi nilai endIndex r.
  11. Jika properti global benar, atur lastIndex ke e.
  12. Biarkan n menjadi panjang array tangkapan r. (Ini adalah nilai yang sama dengan NCapturingParens 15.10.2.1.)
  13. Kembalikan array baru dengan properti berikut:
    • Properti indeks diatur ke posisi substring yang cocok dalam string lengkap S.
    • Properti input diatur ke S.
    • Properti panjang diatur ke n +1.
    • Properti 0 diatur ke substring yang cocok (yaitu bagian S antara offset i inklusif dan offset e eksklusif).
    • Untuk setiap integer i sedemikian rupa sehingga I> 0 dan I ≤ n, atur properti bernama ToString (i) ke elemen ke-i dari array captures r.
Ionuț G. Stan
sumber
83
Ini seperti Panduan Hitchhiker untuk desain Galaxy API di sini. "Perangkap yang Anda terjatuh telah didokumentasikan dengan sempurna dalam spesifikasi selama beberapa tahun, jika Anda hanya ingin memeriksanya"
Retsam
5
Bendera lengket Firefox tidak melakukan apa yang Anda maksudkan sama sekali. Sebaliknya, ia bertindak seolah-olah ada ^ di awal ekspresi reguler, KECUALI bahwa ini ^ cocok dengan posisi string saat ini (lastIndex) daripada awal string. Anda secara efektif menguji apakah regex cocok dengan "di sini" dan bukan "di mana saja setelah lastIndex". Lihat tautan yang Anda berikan!
Lakukan
1
Pernyataan pembuka dari jawaban ini tidak akurat. Anda menyoroti langkah 3 dari spesifikasi yang tidak mengatakan apa-apa. Pengaruh aktual dari lastIndexadalah dalam langkah 5, 6 dan 11. Pernyataan pembuka Anda hanya benar JIKA BENDERA GLOBAL SET.
Prestaul
@Prestaul ya, Anda benar karena tidak menyebutkan bendera global. Mungkin (tidak ingat apa yang saya pikirkan saat itu) tersirat karena cara pertanyaannya dibingkai. Silakan mengedit jawaban atau menghapusnya dan menautkan ke jawaban Anda. Juga, izinkan saya meyakinkan Anda bahwa Anda lebih baik dari saya. Nikmati!
Ionuț G. Stan
@ IonuțG.Stan, maaf jika komentar saya sebelumnya tampak menyerang, itu bukan maksud saya. Saya tidak dapat mengeditnya pada saat ini, tetapi saya tidak mencoba berteriak, hanya untuk menarik perhatian pada poin penting dari komentar saya. Salahku!
Prestaul
72

Anda menggunakan RegExpobjek tunggal dan menjalankannya beberapa kali. Pada setiap eksekusi berturut-turut, ini berlanjut dari indeks pertandingan terakhir.

Anda perlu "mengatur ulang" regex untuk memulai dari awal sebelum setiap eksekusi:

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

Setelah mengatakan bahwa mungkin lebih mudah dibaca untuk membuat objek RegExp baru setiap kali (overhead minimal karena RegExp di-cache juga):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));
Roatin Marth
sumber
1
Atau jangan gunakan gbendera.
melpomene
36

RegExp.prototype.testmemperbarui lastIndexproperti ekspresi reguler sehingga setiap tes akan mulai dari yang terakhir dihentikan. Saya sarankan menggunakan String.prototype.matchkarena tidak memperbarui lastIndexproperti:

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

Catatan: !!mengonversinya menjadi boolean dan kemudian membalikkan boolean sehingga mencerminkan hasilnya.

Atau, Anda bisa mereset lastIndexproperti:

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

Menghapus gbendera global akan memperbaiki masalah Anda.

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

Seharusnya

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

Anda perlu mengatur re.lastIndex = 0 karena dengan flag g regex melacak pertandingan terakhir terjadi, jadi tes tidak akan menguji string yang sama, untuk itu Anda perlu melakukan 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)

Ashish
sumber
-1

Saya memiliki fungsi:

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

Panggilan pertama berfungsi. Panggilan kedua tidak. The sliceOperasi mengeluh tentang nilai null. Saya menganggap ini karena re.lastIndex. Ini aneh karena saya mengharapkan yang baruRegExp dialokasikan setiap kali fungsi dipanggil dan tidak dibagi di beberapa pemanggilan fungsi saya.

Ketika saya mengubahnya menjadi:

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

Maka saya tidak mendapatkan lastIndexefek penahanan. Ini berfungsi seperti yang saya harapkan.

Chelmite
sumber