Reemplazar un grupo de captura Regex con mayúsculas en Javascript

81

Me gustaría saber cómo reemplazar un grupo de captura con su mayúscula en JavaScript. Aquí hay una versión simplificada de lo que he probado hasta ahora que no funciona:

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

¿Podría explicar qué está mal con este código?

Evan Carroll
fuente
@Erik no elimina un componente de una pregunta. También quiero saber por qué mi código falla.
Evan Carroll
Evan, pensé que estaba siendo respetuoso con tu pregunta. Solo eliminé cosas que parecían innecesarias. Dado que proporcionó el código que estaba probando, y obviamente no estaba funcionando, la gente sabía implícitamente que necesitaba una explicación de por qué sin tener que decirlo (y de manera incómoda). ¡Sólo trato de ayudar! :)
ErikE
1
Evan, ¿está mejor? No es mi intención molestar. Si retrocede nuevamente, no volveré a editar, pero ¿podría al menos mantener las ediciones de título y etiqueta en su lugar?
ErikE
Técnicamente, no estoy usando Javascript en absoluto, estoy usando v8 (ECMAScript). Pero, imagino que la mayoría de las personas que busquen esto buscarán JavaScript, así que estoy bien con eso.
Evan Carroll
1
Siéntase libre de volver a agregar etiquetas si cree que pertenecen.
ErikE

Respuestas:

159

Puede pasar una función a replace.

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

Explicación

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

En este ejemplo, pasa una cadena a la función de reemplazo. Dado que está utilizando la sintaxis de reemplazo especial ($ N toma la captura Nth) , simplemente está dando el mismo valor. En toUpperCaserealidad, es engañoso porque solo está poniendo la cadena de reemplazo en mayúsculas (lo cual es algo inútil porque los caracteres $y uno 1no tienen mayúsculas, por lo que el valor de retorno seguirá siendo "$1") .

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

Lo crea o no, la semántica de esta expresión es exactamente la misma.

ChaosPandion
fuente
@Evan Carroll: Vea mi respuesta.
kay
4
Ah, ya veo lo que quieres decir, estoy subrayando "\ $ 1". No es el resultado del vudú que sustituirá al que aparentemente sustituye $1al primer grupo de captura.
Evan Carroll
@EvanCarroll para obtener una explicación detallada de por qué su código inicial no funcionó y cómo hacer que funcione, vea mi respuesta a continuación.
Joshua Piccari
15

Sé que llego tarde a la fiesta, pero aquí hay un método más corto que está más en la línea de sus intentos iniciales.

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

Entonces, ¿en qué te equivocaste y qué es este nuevo vudú?

Problema 1

Como se indicó anteriormente, estaba intentando pasar los resultados de un método llamado como el segundo parámetro de String.prototype.replace () , cuando en su lugar debería pasar una referencia a una función

Solución 1

Eso es bastante fácil de resolver. Simplemente eliminar los parámetros y los paréntesis nos dará una referencia en lugar de ejecutar la función.

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

Problema 2

Si intenta ejecutar el código ahora, obtendrá un error que indica que undefined no es una función y, por lo tanto, no se puede llamar. Esto se debe a que String.prototype.toUpperCase.apply es en realidad una referencia a Function.prototype.apply () a través de la herencia prototípica de JavaScript. Entonces, lo que estamos haciendo se parece más a esto

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

Que obviamente no es lo que pretendíamos. ¿Cómo sabe ejecutar Function.prototype.apply () en String.prototype.toUpperCase () ?

Solucion 2

Usando Function.prototype.bind () podemos crear una copia de Function.prototype.call con su contexto específicamente establecido en String.prototype.toUpperCase. Ahora tenemos lo siguiente

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

Problema 3

El último problema es que String.prototype.replace () pasará varios argumentos a su función de reemplazo. Sin embargo, Function.prototype.apply () espera que el segundo parámetro sea una matriz, pero en cambio obtiene una cadena o un número (dependiendo de si usa grupos de captura o no). Esto provocaría un error de lista de argumentos no válidos.

Solución 3

Afortunadamente, podemos simplemente sustituir Function.prototype.call () (que acepta cualquier número de argumentos, ninguno de los cuales tiene restricciones de tipo) por Function.prototype.apply () . ¡Hemos llegado al código de trabajo!

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

¡Eliminando bytes!

Nadie quiere escribir un prototipo muchas veces. En su lugar, aprovecharemos el hecho de que tenemos objetos que hacen referencia a los mismos métodos a través de la herencia. El constructor String, al ser una función, hereda del prototipo de Function. Esto significa que podemos sustituir String.call por Function.prototype.call (en realidad, podemos usar Date.call para guardar aún más bytes, pero eso es menos semántico).

También podemos aprovechar nuestra variable 'a' ya que su prototipo incluye una referencia a String.prototype.toUpperCase , podemos intercambiar eso con a.toUpperCase. Es la combinación de las 3 soluciones anteriores y estas medidas de ahorro de bytes que es como obtenemos el código en la parte superior de esta publicación.

Joshua Piccari
fuente
4
Guardaste 8 caracteres, mientras que ocultaste el código de tal manera que requirió una página de explicación sobre la solución más obvia. No estoy convencido de que esto sea una victoria.
Lawrence Dol
1
Intelectualmente, esta es una gran solución, ya que muestra / enseña una o dos cosas sobre las funciones de JavaScript. Pero estoy con Lawrence que en la práctica es demasiado oscuro para ser utilizado. Todavía está bien.
meetamit
9

Publicación anterior, pero vale la pena extender la respuesta de @ChaosPandion para otros casos de uso con expresiones regulares más restringidas. Por ejemplo, asegúrese de que el (f)grupo de captura o el entorno envolvente con un formato específico /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.

Solo quiero resaltar los dos parámetros de functionhace posible encontrar un formato específico y reemplazar un grupo de captura dentro del formato.

LlamarMeLaNN
fuente
¡Gracias! Las publicaciones anteriores parecen haber respondido al problema de por qué el código del OP no funcionó mientras se saltaba por completo lo que parecía el punto real para mí: ¡reemplazar un grupo de coincidencia!
Auspex
Creo que tiene un error en su función de reemplazo, pero compruébelo. Creo que debería ser return $0.replace($0, $1.toUpperCase()), ¿dónde $0está el primer argumento
Mike
Era una cadena simple para reemplazar la cadena. entonces f a F es correcto.
CallMeLaNN
¡Esto es realmente útil si está intentando reemplazar algo entre paréntesis!
Diodo Dan
3

¿Por qué no buscamos la definición ?

Si escribimos:

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

bien podríamos decir:

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

Peor aún, sospecho que nadie se da cuenta de que sus ejemplos han estado funcionando solo porque estaban capturando toda la expresión regular entre paréntesis. Si observa la definición , el primer parámetro que se pasa a la replacerfunción es en realidad el patrón coincidente completo y no el patrón que capturó entre paréntesis:

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

Si desea utilizar la notación de función de flecha:

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

SOLUCIÓN

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

para reemplazar todas las ocurrencias de grup, use /(f)/gregexp. El problema en su código: String.prototype.toUpperCase.apply("$1")y "$1".toUpperCase()da "$1"(pruebe en la consola usted mismo) - por lo que no cambia nada y de hecho llama dos veces a.replace( /(f)/, "$1")(que tampoco cambia nada).

Kamil Kiełczewski
fuente
0

Dado un diccionario (objeto, en este caso, a Map) de propiedad, valores y uso .bind()como se describe en las respuestas

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

Usando un objeto simple de JavaScript y con una función definida para obtener el valor de propiedad de retorno del objeto, o cadena original si no se encuentra

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

invitado271314
fuente