Zastąp grupę przechwytywania Regex wielkimi literami w JavaScript

81

Chciałbym wiedzieć, jak zamienić grupę przechwytywania na wielkie litery w JavaScript. Oto uproszczona wersja tego, co próbowałem do tej pory, ale nie działa:

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

Czy możesz wyjaśnić, co jest nie tak z tym kodem?

Evan Carroll
źródło
@Erik nie usuwa składnika pytania. Chcę wiedzieć, dlaczego mój kod również zawodzi.
Evan Carroll
Evan, myślałem, że szanuję twoje pytanie. Usunąłem tylko rzeczy, które wydawały się niepotrzebne. Odkąd podałeś kod, który próbowałeś, i oczywiście nie działał, ludzie w domyśle wiedzieli, że potrzebujesz wyjaśnienia, dlaczego nie musisz tego mówić (i niezręcznie). Po prostu próbuje pomóc! :)
ErikE
1
Evan, czy tak jest lepiej? Nie chcę denerwować. Jeśli cofniesz zmiany, nie będę ponownie edytować, ale czy możesz przynajmniej zachować zmiany w tytule i tagu?
ErikE
Technicznie rzecz biorąc, w ogóle nie używam JavaScript, używam v8 (ECMAScript). Ale wyobrażam sobie, że większość osób wyszukujących to będzie szukać JavaScript, więc jestem w tym dobry.
Evan Carroll
1
Możesz z powrotem dodać tagi, jeśli uważasz, że należą.
ErikE

Odpowiedzi:

159

Możesz przekazać funkcję replace.

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

Wyjaśnienie

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

W tym przykładzie przekazujesz ciąg do funkcji replace. Ponieważ używasz specjalnej składni zamiany ($ N przechwytuje N-ty przechwycenie) , po prostu podajesz tę samą wartość. toUpperCaseJest rzeczywiście mylące, ponieważ jesteś tylko dzięki czemu zastąpić ciąg dużych liter (co jest trochę bez sensu, bo $i jedno 1znaków nie ma dużych liter więc wartość zwracana będzie nadal "$1") .

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

Wierz lub nie, semantyka tego wyrażenia jest dokładnie taka sama.

ChaosPandion
źródło
@Evan Carroll: Zobacz moją odpowiedź.
kay
4
Ach, rozumiem, co masz na myśli, zwiększam wielkość „\ $ 1”. To nie wynik działania voodoo, które zastąpi, nie jest widocznym zastąpieniem $1pierwszej grupy przechwytywania.
Evan Carroll
@EvanCarroll, aby uzyskać dokładne wyjaśnienie, dlaczego Twój początkowy kod nie zadziałał i jak go uruchomić, zobacz moją odpowiedź poniżej.
Joshua Piccari
15

Wiem, że spóźniłem się na imprezę, ale tutaj jest krótsza metoda, która jest bardziej podobna do twoich początkowych prób.

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

Więc gdzie popełniłeś błąd i co to za nowe voodoo?

Zadanie 1

Jak wspomniano wcześniej, próbujesz przekazać wyniki wywoływanej metody jako drugi parametr metody String.prototype.replace () , podczas gdy zamiast tego powinieneś przekazać referencję do funkcji

Rozwiązanie 1

To dość łatwe do rozwiązania. Po prostu usunięcie parametrów i nawiasów da nam odniesienie zamiast wykonywania funkcji.

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

Problem 2

Jeśli spróbujesz teraz uruchomić kod, pojawi się błąd informujący, że undefined nie jest funkcją i dlatego nie można go wywołać. Dzieje się tak, ponieważ String.prototype.toUpperCase.apply jest w rzeczywistości odwołaniem do funkcji Function.prototype.apply () poprzez prototypowe dziedziczenie JavaScript. Więc to, co faktycznie robimy, wygląda bardziej tak

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

Co oczywiście nie jest tym, czego chcieliśmy. Skąd wie, że należy uruchomić Function.prototype.apply () na String.prototype.toUpperCase () ?

Rozwiązanie 2

Używając funkcji Function.prototype.bind () możemy utworzyć kopię Function.prototype.call z jej kontekstem ustawionym specjalnie na String.prototype.toUpperCase. Mamy teraz następujące

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

Problem 3

Ostatnią kwestią jest to, że String.prototype.replace () przekaże kilka argumentów do swojej funkcji zastępującej. Jednak funkcja Function.prototype.apply () oczekuje, że drugi parametr będzie tablicą, ale zamiast tego pobiera ciąg lub liczbę (w zależności od tego, czy używasz grup przechwytywania, czy nie). Spowodowałoby to nieprawidłowy błąd listy argumentów.

Rozwiązanie 3

Na szczęście możemy po prostu podstawić w Function.prototype.call () (który akceptuje dowolną liczbę argumentów, z których żaden nie ma ograniczeń typu) dla Function.prototype.apply () . Doszliśmy do działającego kodu!

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

Utrata bajtów!

Nikt nie chce pisać prototypu kilka razy. Zamiast tego wykorzystamy fakt, że mamy obiekty, które odwołują się do tych samych metod poprzez dziedziczenie. Konstruktor String, będący funkcją, dziedziczy po prototypie Function. Oznacza to, że w String.call możemy zastąpić Function.prototype.call (w rzeczywistości możemy użyć Date.call, aby zapisać jeszcze więcej bajtów, ale to mniej semantyczne).

Możemy również wykorzystać naszą zmienną „a”, ponieważ jej prototyp zawiera odniesienie do String.prototype.toUpperCase, które możemy zamienić na a.toUpperCase. Jest to połączenie powyższych 3 rozwiązań i tych środków oszczędzania bajtów, dzięki czemu otrzymujemy kod na początku tego postu.

Joshua Piccari
źródło
4
Zaoszczędziłeś 8 znaków, zasłaniając kod w taki sposób, że wymaganie strony z wyjaśnieniami zamiast bardziej oczywistego rozwiązania. Nie jestem przekonany, że to wygrana.
Lawrence Dol
1
Z intelektualnego punktu widzenia jest to świetne rozwiązanie, ponieważ ujawnia / uczy 2 rzeczy o funkcjach javascript. Ale jestem z Lawrence'em, że w praktyce jest to zbyt niejasne, aby faktycznie było używane. Wciąż super.
Meetamit
9

Stary post, ale warto rozszerzyć odpowiedź @ChaosPandion na inne przypadki użycia z bardziej ograniczonym wyrażeniem regularnym. Np. Upewnij się, że (f)lub przechwytuj grupę surround w określonym formacie /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.

Chcę tylko podkreślić dwa parametry functionumożliwiające znalezienie określonego formatu i zastąpienie grupy przechwytywania w tym formacie.

CallMeLaNN
źródło
Dziękuję Ci! Wydaje się, że poprzednie posty rozwiązały problem, dlaczego kod OP nie działa, całkowicie pomijając to, co wydawało mi się prawdziwym celem - zastępując grupę meczową!
Auspex
Myślę, że masz błąd w funkcji zamiany, ale sprawdź mnie na tym. Myślę, że powinno być return $0.replace($0, $1.toUpperCase()), gdzie $0jest pierwszy argument
Mike
Był to prosty ciąg do zastąpienia. więc f do F jest poprawne.
CallMeLaNN
Jest to bardzo pomocne, jeśli próbujesz zastąpić coś w nawiasach!
Dioda Dan
3

Dlaczego po prostu nie sprawdzimy definicji ?

Jeśli napiszemy:

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

równie dobrze możemy po prostu powiedzieć:

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

Co gorsza, podejrzewam, że nikt nie zdaje sobie sprawy, że ich przykłady działają tylko dlatego, że przechwytują całe wyrażenie regularne w nawiasach. Jeśli spojrzysz na definicję , pierwszym parametrem przekazanym do replacerfunkcji jest w rzeczywistości cały dopasowany wzorzec, a nie wzorzec, który przechwyciłeś w nawiasach:

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

Jeśli chcesz użyć notacji funkcji strzałek:

a.replace(/xxx(yyy)zzz/, (match, p1) => p1.toUpperCase()
Bernhard Wagner
źródło
1

ROZWIĄZANIE

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

aby zamienić wszystkie wystąpienia grup, użyj /(f)/gwyrażenia regularnego. Problem w twoim kodzie: String.prototype.toUpperCase.apply("$1")i "$1".toUpperCase()daje "$1"(spróbuj sam w konsoli) - więc nic nie zmienia i faktycznie dzwonisz dwa razy a.replace( /(f)/, "$1")(co też nic nie zmienia).

Kamil Kiełczewski
źródło
0

Biorąc pod uwagę słownik (obiekt, w tym przypadku a Map) właściwości, wartości i użycie, .bind()jak opisano w odpowiedziach

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

Używanie zwykłego obiektu JavaScript i funkcji zdefiniowanej w celu zwrócenia dopasowanej wartości właściwości obiektu lub oryginalnego ciągu, jeśli nie zostanie znalezione dopasowanie

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

gość271314
źródło