javascript - Hvorfor er "asdf".replace(/.*/g, "x") == "xx"?
Jeg snublet over et overraskende (for meg) faktum.
console.log("asdf".replace(/.*/g, "x"));Hvorfor to erstatninger? Det ser ut til at enhver ikke-tom streng uten linjeskift vil gi nøyaktig to erstatninger for dette mønsteret. Ved å bruke en erstatningsfunksjon kan jeg se at den første erstatningen gjelder for hele strengen, og den andre gjelder for en tom streng.
Svar #1
I henhold til ECMA-262-standarden kaller String.prototype.replace RegExp.prototype[@@replace], som sier:
11. Repeat, while done is false
a. Let result be ? RegExpExec(rx, S).
b. If result is null, set done to true.
c. Else result is not null,
i. Append result to the end of results.
ii. If global is false, set done to true.
iii. Else,
1. Let matchStr be ? ToString(? Get(result, "0")).
2. If matchStr is the empty String, then
a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")).
b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
c. Perform ? Set(rx, "lastIndex", nextIndex, true).
hvor rx er /.*/g og S er 'asdf'.
Se 11.c.iii.2.b:
b. La nextIndex være AdvanceStringIndex(S, thisIndex, fullUnicode).
Derfor i 'asdf'.replace(/.*/g, 'x') er det faktisk:
- resultat (udefinert), resultater=
[], sisteIndeks=0 - resultat=
'asdf', resultater=[ 'asdf' ], sisteIndeks=4 - result=
'', results=[ 'asdf', '' ], lastIndex=4,AdvanceStringIndex, sett lastIndex til5 - resultat=
null, resultater=[ 'asdf', '' ], returner
Derfor er det 2 kamper.
Svar #2
Sammen i en offline-chat med yawkat fant vi en intuitiv måte å se hvorfor "abcd".replace(/.*/g, "x") nøyaktig produserer to treff. Merk at vi ikke har sjekket om det samsvarer fullstendig med semantikken som er angitt av ECMAScript-standarden, så ta det derfor bare som en tommelfingerregel.
Tommelregler
- Vurder treffene som en liste over tupler
(matchStr, matchIndex)i kronologisk rekkefølge som indikerer hvilke deler av strengen og indekser av inndatastrengen som allerede er brukt. - Denne listen bygges kontinuerlig opp med start fra venstre side av input-strengen for regexen.
- Deler som allerede er spist opp, kan ikke lenger matches
- Erstatning utføres på indekser gitt av
matchIndexog overskriver delstrengenmatchStrpå den posisjonen. HvismatchStr="", er da "erstatningen" effektivt en innsetting.
Formelt blir handlingen med matching og erstattelse beskrevet som en løkke, som sett i det andre svaret.
Enkle eksempler
"abcd".replace(/.*/g, "x")gir"xx":Matchlisten er
[("abcd", 0), ("", 4)]Merkelig nok inkluderer det ikke følgende kamper man kunne ha tenkt på av følgende grunner:
("a", 0),("ab", 0): kvantifikatoren*er grådig
- Matchlisten er
[("abcd", 0), ("", 4)]. Merk at den ikke inkluderer følgende treff som man kunne ha tenkt på av følgende grunner: ("a", 0),("ab", 0): kvantifiseren*er grådig("b", 1),("bc", 1): på grunn av det forrige treffet("abcd", 0), er strengene"b"og"bc"allerede brukt opp("", 4), ("", 4)(dvs. to ganger): indeksposisjon 4 er allerede brukt opp av det første tilsynelatende treffetDermed erstatter erstatningsstrengen
"x"de funnet matchstrengene nøyaktig på disse posisjonene: ved posisjon 0 erstatter den strengen"abcd"og ved posisjon 4 erstatter den"".Her kan du se at erstattning kan fungere som en faktisk erstatning for en tidligere streng eller bare som innsetting av en ny streng.
"abcd".replace(/.*?/g, "x")med en lat kvantifikator*?gir"xaxbxcxdx"Matchlisten er
[("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]I motsetning til det forrige eksemplet, her er
("a", 0),("ab", 0),("abc", 0)eller til og med("abcd", 0)ikke inkludert på grunn av kvantorens treghet som strengt begrenser den til å finne den korteste mulige matchen.
Siden alle samsvarende strenger er tomme, skjer ingen faktisk erstatning, men i stedet settes
xinn på posisjonene 0, 1, 2, 3 og 4."abcd".replace(/.+?/g, "x")med en lat kvantor+?gir"xxxx"- Kampelisten er
[("a", 0), ("b", 1), ("c", 2), ("d", 3)]
- Kampelisten er
"abcd".replace(/.{2,}?/g, "x")med en lat kvantifikator[2,}?gir"xx"- Matchlisten er
[("ab", 0), ("cd", 2)]
- Matchlisten er
"abcd".replace(/.{0}/g, "x")gir"xaxbxcxdx"etter samme logikk som i eksempel 2.
Vanskeligere eksempler
Vi kan konsekvent utnytte ideen om innsetting i stedet for erstatning hvis vi bare alltid matcher en tom streng og kontrollerer posisjonen der slike treff skjer til vår fordel. For eksempel kan vi lage regulære uttrykk som matcher den tomme strengen på hver jevn posisjon for å sette inn et tegn der:
"abcdefgh".replace(/(?<=^(..)*)/g, "_"))med et positivt lookbehind(?<=...)gir"_ab_cd_ef_gh_"(kun støttet i Chrome så langt)- Matchlisten er
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
- Matchlisten er
"abcdefgh".replace(/(?=(..)*$)/g, "_"))med et positivt lookahead(?=...)gir"_ab_cd_ef_gh_"- Matchlisten er
[("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
- Matchlisten er
Svar #3
Det første treffet er åpenbart "asdf" (Posisjon [0,4]). Fordi global flagg (g) er satt, fortsetter det å søke. På dette punktet (Posisjon 4) finner det et andre treff, en tom streng (Posisjon [4,4]).
Husk at * matcher null eller flere elementer.
