Ersetzen Sie eine Regex-Erfassungsgruppe durch Großbuchstaben in Javascript

80

Ich möchte wissen, wie man eine Erfassungsgruppe in JavaScript durch Großbuchstaben ersetzt. Hier ist eine vereinfachte Version von dem, was ich bisher versucht habe und was nicht funktioniert:

> a="foobar"
'foobar'
> a.replace( /(f)/, "$1".toUpperCase() )
'foobar'
> a.replace( /(f)/, String.prototype.toUpperCase.apply("$1") )
'foobar'

Würden Sie erklären, was mit diesem Code nicht stimmt?

Evan Carroll
quelle
@Erik entfernt keine Komponente einer Frage. Ich möchte wissen, warum mein Code ebenfalls fehlschlägt.
Evan Carroll
Evan, ich dachte, ich respektiere deine Frage. Ich habe nur Dinge entfernt, die unnötig erschienen. Da Sie den Code angegeben haben, den Sie versucht haben, und er offensichtlich nicht funktioniert hat, wussten die Leute implizit, dass Sie eine Erklärung benötigen, warum, ohne dass Sie dies sagen müssen (und dies umständlich). Ich versuche nur zu helfen! :)
ErikE
1
Evan, ist das besser? Ich will nicht nerven. Wenn Sie erneut ein Rollback durchführen, werde ich nicht erneut bearbeiten. Könnten Sie jedoch zumindest die Titel- und Tag-Änderungen beibehalten?
ErikE
Technisch gesehen verwende ich überhaupt kein Javascript, sondern v8 (ECMAScript). Aber ich kann mir vorstellen, dass die meisten Leute, die dies suchen, nach JavaScript suchen, also bin ich gut damit.
Evan Carroll
1
Fühlen Sie sich frei, Tags wieder hinzuzufügen, wenn Sie denken, dass sie dazu gehören.
ErikE

Antworten:

157

Sie können eine Funktion an übergeben replace.

var r = a.replace(/(f)/, function(v) { return v.toUpperCase(); });

Erläuterung

a.replace( /(f)/, "$1".toUpperCase())

In diesem Beispiel übergeben Sie eine Zeichenfolge an die Ersetzungsfunktion. Da Sie die spezielle Ersetzungssyntax verwenden ($ N greift nach dem N-ten Capture), geben Sie einfach den gleichen Wert an. Das toUpperCasetäuscht tatsächlich, weil Sie nur die Ersetzungszeichenfolge in Großbuchstaben schreiben (was etwas sinnlos ist, da die Zeichen $und ein 1Zeichen keine Großbuchstaben haben, sodass der Rückgabewert weiterhin erhalten bleibt "$1") .

a.replace( /(f)/, String.prototype.toUpperCase.apply("$1"))

Ob Sie es glauben oder nicht, die Semantik dieses Ausdrucks ist genau dieselbe.

ChaosPandion
quelle
@Evan Carroll: Bitte sehen Sie meine Antwort.
Kay
4
Ah, ich verstehe, was du meinst, ich übertreibe "\ $ 1". Nicht das Ergebnis des Voodoos, das ersetzt wird, ersetzt anscheinend $1die erste Erfassungsgruppe.
Evan Carroll
@EvanCarroll für eine ausführliche Erklärung, warum Ihr ursprünglicher Code nicht funktioniert hat und wie Sie ihn zum Laufen bringen können, siehe meine Antwort unten.
Joshua Piccari
15

Ich weiß, dass ich zu spät zur Party komme, aber hier ist eine kürzere Methode, die eher Ihren ersten Versuchen entspricht.

a.replace('f', String.call.bind(a.toUpperCase));

Wo hast du etwas falsch gemacht und was ist das für ein neues Voodoo?

Problem 1

Wie bereits erwähnt, haben Sie versucht, die Ergebnisse einer aufgerufenen Methode als zweiten Parameter von String.prototype.replace () zu übergeben , wenn Sie stattdessen einen Verweis auf eine Funktion übergeben sollten

Lösung 1

Das ist leicht zu lösen. Durch einfaches Entfernen der Parameter und Klammern erhalten wir eine Referenz, anstatt die Funktion auszuführen.

a.replace('f', String.prototype.toUpperCase.apply)

Problem 2

Wenn Sie versuchen, den Code jetzt auszuführen, wird eine Fehlermeldung angezeigt, dass undefined keine Funktion ist und daher nicht aufgerufen werden kann. Dies liegt daran, dass String.prototype.toUpperCase.apply tatsächlich ein Verweis auf Function.prototype.apply () über die prototypische Vererbung von JavaScript ist. Was wir also tatsächlich tun, sieht eher so aus

a.replace('f', Function.prototype.apply)

Welches ist offensichtlich nicht das, was wir beabsichtigt haben. Woher weiß es, Function.prototype.apply () auf String.prototype.toUpperCase () auszuführen ?

Lösung 2

Mit Function.prototype.bind () können wir eine Kopie von Function.prototype.call erstellen, deren Kontext speziell auf String.prototype.toUpperCase festgelegt ist. Wir haben jetzt folgendes

a.replace('f', Function.prototype.apply.bind(String.prototype.toUpperCase))

Problem 3

Das letzte Problem ist, dass String.prototype.replace () mehrere Argumente an seine Ersetzungsfunktion übergibt . Function.prototype.apply () erwartet jedoch, dass der zweite Parameter ein Array ist, erhält jedoch entweder eine Zeichenfolge oder eine Zahl (je nachdem, ob Sie Erfassungsgruppen verwenden oder nicht). Dies würde einen ungültigen Argumentlistenfehler verursachen.

Lösung 3

Glücklicherweise können wir Function.prototype.apply () einfach in Function.prototype.call () ersetzen (das eine beliebige Anzahl von Argumenten akzeptiert, von denen keines Typeinschränkungen aufweist) . Wir sind jetzt beim Arbeitscode angekommen!

a.replace(/f/, Function.prototype.call.bind(String.prototype.toUpperCase))

Bytes verlieren!

Niemand möchte ein paar Mal einen Prototyp schreiben. Stattdessen nutzen wir die Tatsache, dass wir Objekte haben, die über die Vererbung auf dieselben Methoden verweisen. Der String-Konstruktor ist eine Funktion und erbt vom Prototyp der Funktion. Dies bedeutet, dass wir Function.prototype.call in String.call ersetzen können (tatsächlich können wir Date.call verwenden, um noch mehr Bytes zu speichern, aber das ist weniger semantisch).

Wir können auch unsere Variable 'a' nutzen, da ihr Prototyp einen Verweis auf String.prototype.toUpperCase enthält. Wir können diesen mit a.toUpperCase austauschen. Durch die Kombination der drei oben genannten Lösungen und dieser Maßnahmen zum Speichern von Bytes erhalten wir den Code oben in diesem Beitrag.

Joshua Piccari
quelle
4
Sie haben 8 Zeichen gespeichert und dabei den Code so verdeckt, dass eine Seite mit Erläuterungen zur offensichtlicheren Lösung erforderlich ist. Ich bin nicht davon überzeugt, dass dies ein Gewinn ist.
Lawrence Dol
1
Intellektuell ist dies eine großartige Lösung, da sie ein oder zwei Dinge über Javascript-Funktionen auftaucht / lehrt. Aber ich bin mit Lawrence zusammen, dass es in der Praxis zu dunkel ist, um tatsächlich verwendet zu werden. Trotzdem cool.
Meetamit
8

Alter Beitrag, aber es lohnt sich, die @ ChaosPandion-Antwort für andere Anwendungsfälle mit eingeschränkterem RegEx zu erweitern. Stellen Sie beispielsweise sicher, dass die (f)oder die Erfassung der Gruppenumgebung ein bestimmtes Format aufweist /z(f)oo/:

> a="foobazfoobar"
'foobazfoobar'
> a.replace(/z(f)oo/, function($0,$1) {return $0.replace($1, $1.toUpperCase());})
'foobazFoobar'
// Improve the RegEx so `(f)` will only get replaced when it begins with a dot or new line, etc.

Ich möchte nur die beiden Parameter hervorheben, die es functionermöglichen, ein bestimmtes Format zu finden und eine Erfassungsgruppe innerhalb des Formats zu ersetzen.

CallMeLaNN
quelle
Vielen Dank! Die vorherigen Beiträge scheinen das Problem beantwortet zu haben, warum der OP-Code nicht funktioniert hat, während ich den eigentlichen Punkt übersprungen habe - das Ersetzen einer Matchgruppe!
Auspex
Ich denke, Sie haben einen Fehler in Ihrer Ersetzungsfunktion, aber überprüfen Sie mich diesbezüglich. Ich denke es sollte sein return $0.replace($0, $1.toUpperCase()), wo $0ist das erste Argument
Mike
Es war eine einfache Zeichenfolge, die ersetzt werden musste. also ist f bis F richtig.
CallMeLaNN
Dies ist sehr hilfreich, wenn Sie versuchen, etwas in Klammern zu ersetzen!
Diode Dan
3

Warum schlagen wir nicht einfach die Definition nach ?

Wenn wir schreiben:

a.replace(/(f)/, x => x.toUpperCase())

wir könnten genauso gut einfach sagen:

a.replace('f','F')

Schlimmer noch, ich vermute, niemand merkt, dass ihre Beispiele nur funktioniert haben, weil sie den gesamten regulären Ausdruck in Klammern festgehalten haben . Wenn Sie sich die Definition ansehen , ist der erste Parameter, der an die replacerFunktion übergeben wird, das gesamte übereinstimmende Muster und nicht das Muster, das Sie in Klammern erfasst haben:

function replacer(match, p1, p2, p3, offset, string)

Wenn Sie die Pfeilfunktionsnotation verwenden möchten:

a.replace(/xxx(yyy)zzz/, (match, p1) => p1.toUpperCase()
Bernhard Wagner
quelle
1

LÖSUNG

a.replace(/(f)/,x=>x.toUpperCase())  

Verwenden Sie /(f)/gregexp, um alle grup-Vorkommen zu ersetzen. Das Problem in Ihrem Code: String.prototype.toUpperCase.apply("$1")und "$1".toUpperCase()gibt "$1"(versuchen Sie es in der Konsole selbst) - so ändert es nichts und Sie rufen tatsächlich zweimal auf a.replace( /(f)/, "$1")(was auch nichts ändert).

Kamil Kiełczewski
quelle
0

Gegeben ein Wörterbuch (Objekt, in diesem Fall a Map) von Eigenschaften, Werten und Verwendung, .bind()wie unter Antworten beschrieben

const regex = /([A-z0-9]+)/;
const dictionary = new Map([["hello", 123]]); 
let str = "hello";
str = str.replace(regex, dictionary.get.bind(dictionary));

console.log(str);

Verwenden eines einfachen JavaScript-Objekts und einer definierten Funktion zum Abrufen des übereinstimmenden Eigenschaftswerts des Objekts oder der ursprünglichen Zeichenfolge, wenn keine Übereinstimmung gefunden wird

const regex = /([A-z0-9]+)/;
const dictionary = {
  "hello": 123,
  [Symbol("dictionary")](prop) {
    return this[prop] || prop
  }
};
let str = "hello";
str = str.replace(regex, dictionary[Object.getOwnPropertySymbols(dictionary)[0]].bind(dictionary));

console.log(str);

guest271314
quelle