Saya menemukan fakta mengejutkan (bagi saya).
console.log("asdf".replace(/.*/g, "x"));
Mengapa dua penggantian? Tampaknya string yang tidak kosong tanpa baris baru akan menghasilkan dua penggantian tepat untuk pola ini. Menggunakan fungsi penggantian, saya bisa melihat bahwa penggantian pertama adalah untuk seluruh string, dan yang kedua adalah untuk string kosong.
javascript
regex
rekursif
sumber
sumber
"asdf".match(/.*/g)
return ["asdf", ""]"aa".replace(/b*/, "b")
untuk menghasilkanbabab
. Dan pada titik tertentu kami menstandarkan semua detail implementasi webbrowser.Jawaban:
Sesuai standar ECMA-262 , String.prototype.replace panggilan RegExp.prototype [@@ replace] , yang mengatakan:
dimana
rx
adalah/.*/g
danS
adalah'asdf'
.Lihat 11.c.iii.2.b:
Oleh karena itu dalam
'asdf'.replace(/.*/g, 'x')
dalamnya sebenarnya:[]
, lastIndex =0
'asdf'
, hasil =[ 'asdf' ]
, lastIndex =4
''
, hasil =[ 'asdf', '' ]
, lastIndex =4
,AdvanceStringIndex
, mengatur lastIndex ke5
null
, hasil =[ 'asdf', '' ]
, kembaliKarena itu ada 2 pertandingan.
sumber
'asdf'
dan mengosongkan string''
.Bersama dalam obrolan offline dengan yawkat , kami menemukan cara yang intuitif melihat mengapa
"abcd".replace(/.*/g, "x")
persis menghasilkan dua kecocokan. Perhatikan bahwa kami belum memeriksa apakah itu benar-benar sama dengan semantik yang dipaksakan oleh standar ECMAScript, maka anggap saja sebagai aturan praktis.Aturan Jempol
(matchStr, matchIndex)
dalam urutan kronologis yang menunjukkan bagian string mana dan indeks dari string input yang telah dimakan.matchIndex
menimpa substringmatchStr
pada posisi itu. JikamatchStr = ""
, maka "penggantian" secara efektif dimasukkan.Secara formal, tindakan pencocokan dan penggantian digambarkan sebagai lingkaran seperti yang terlihat pada jawaban lainnya .
Contoh mudah
"abcd".replace(/.*/g, "x")
output"xx"
:Daftar pertandingan adalah
[("abcd", 0), ("", 4)]
Khususnya, itu tidak termasuk pertandingan berikut yang orang bisa pikirkan karena alasan berikut:
("a", 0)
,("ab", 0)
: quantifier*
serakah("b", 1)
,("bc", 1)
: karena pertandingan sebelumnya("abcd", 0)
, senar"b"
dan"bc"
sudah dimakan("", 4), ("", 4)
(Yaitu dua kali): posisi indeks 4 sudah dimakan oleh pertandingan nyata pertamaOleh karena itu, string pengganti
"x"
menggantikan string yang ditemukan tepat pada posisi tersebut: pada posisi 0 itu menggantikan string"abcd"
dan pada posisi 4 itu menggantikan""
.Di sini Anda dapat melihat bahwa penggantian dapat berfungsi sebagai penggantian yang benar dari string sebelumnya atau hanya sebagai penyisipan string baru.
"abcd".replace(/.*?/g, "x")
dengan keluaran kuantifier malas*?
"xaxbxcxdx"
Daftar pertandingan adalah
[("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]
Berbeda dengan contoh sebelumnya, di sini
("a", 0)
,("ab", 0)
,("abc", 0)
, atau bahkan("abcd", 0)
tidak termasuk karena kemalasan quantifier yang ketat membatasi untuk menemukan pertandingan yang sesingkat mungkin.Karena semua string kecocokan kosong, tidak ada penggantian yang sebenarnya terjadi, melainkan penempatan
x
pada posisi 0, 1, 2, 3, dan 4."abcd".replace(/.+?/g, "x")
dengan keluaran kuantifier malas+?
"xxxx"
[("a", 0), ("b", 1), ("c", 2), ("d", 3)]
"abcd".replace(/.{2,}?/g, "x")
dengan keluaran kuantifier malas[2,}?
"xx"
[("ab", 0), ("cd", 2)]
"abcd".replace(/.{0}/g, "x")
output"xaxbxcxdx"
dengan logika yang sama seperti pada contoh 2.Contoh yang lebih sulit
Kami dapat secara konsisten mengeksploitasi gagasan penyisipan dan bukan penggantian jika kami hanya selalu mencocokkan string kosong dan mengontrol posisi di mana kecocokan tersebut terjadi untuk keuntungan kami. Sebagai contoh, kita dapat membuat ekspresi reguler yang cocok dengan string kosong di setiap posisi genap untuk menyisipkan karakter di sana:
"abcdefgh".replace(/(?<=^(..)*)/g, "_"))
dengan lookbehind positif(?<=...)
output"_ab_cd_ef_gh_"
(hanya didukung di Chrome sejauh ini)[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
"abcdefgh".replace(/(?=(..)*$)/g, "_"))
dengan output lookahead positif(?=...)
"_ab_cd_ef_gh_"
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
sumber
while (!input not eaten up) { matchAndEat(); }
. Juga, komentar di atas menunjukkan bahwa perilaku berasal dari jauh sebelum keberadaan JavaScript.("abcd", 0)
tidak memakan posisi 4 di mana karakter berikut akan pergi, namun pertandingan karakter nol("", 4)
tidak makan posisi 4 di mana karakter berikut akan pergi. Jika saya mendesain ini dari awal, saya pikir aturan yang akan saya gunakan adalah yang(str2, ix2)
mungkin mengikuti(str1, ix1)
iffix2 >= ix1 + str1.length() && ix2 + str2.length() > ix1 + str1.length()
, yang tidak menyebabkan kesalahan ini.("abcd", 0)
tidak memakan posisi 4"abcd"
karena panjangnya hanya 4 karakter dan karenanya hanya makan indeks 0, 1, 2, 3. Saya bisa melihat dari mana alasan Anda berasal: mengapa kita tidak bisa memiliki("abcd" ⋅ ε, 0)
pertandingan 5 karakter di mana ⋅ Apakah gabungan danε
kesesuaian nol-lebar? Karena secara formal"abcd" ⋅ ε = "abcd"
. Saya memikirkan alasan intuitif untuk menit-menit terakhir, tetapi gagal menemukannya. Saya kira kita harus selalu memperlakukanε
hanya sebagai terjadi dengan sendirinya""
. Saya ingin bermain dengan implementasi alternatif tanpa bug atau fitur itu, silakan bagikan!"" ⋅ ε = ""
, meskipun saya tidak yakin perbedaan apa yang ingin Anda tarik antara""
danε
, yang berarti hal yang sama). Jadi perbedaannya tidak bisa dijelaskan sebagai intuitif — memang begitu.Pertandingan pertama jelas
"asdf"
(Posisi [0,4]). Karena flag global (g
) diatur, ia melanjutkan pencarian. Pada titik ini (Posisi 4), ia menemukan kecocokan kedua, string kosong (Posisi [4,4]).Ingat bahwa
*
cocok dengan nol atau lebih elemen.sumber
sederhananya, yang pertama
x
adalah untuk penggantian yang cocokasdf
.kedua
x
untuk string kosong sesudahnyaasdf
. Pencarian berakhir ketika kosong.sumber