`String.prototype.replaceAll`
Si alguna vez has trabajado con cadenas en JavaScript, es probable que te hayas encontrado con el método String#replace
. String.prototype.replace(searchValue, replacement)
devuelve una cadena con algunas coincidencias reemplazadas, basándose en los parámetros que especifiques:
'abc'.replace('b', '_');
// → 'a_c'
'🍏🍋🍊🍓'.replace('🍏', '🥭');
// → '🥭🍋🍊🍓'
Un caso de uso común es reemplazar todas las instancias de una subcadena dada. Sin embargo, String#replace
no aborda directamente este caso de uso. Cuando searchValue
es una cadena, solo se reemplaza la primera aparición de la subcadena:
'aabbcc'.replace('b', '_');
// → 'aa_bcc'
'🍏🍏🍋🍋🍊🍊🍓🍓'.replace('🍏', '🥭');
// → '🥭🍏🍋🍋🍊🍊🍓🍓'
Para resolver esto, los desarrolladores a menudo convierten la cadena de búsqueda en una expresión regular con el indicador global (g
). De esta manera, String#replace
reemplaza todas las coincidencias:
'aabbcc'.replace(/b/g, '_');
// → 'aa__cc'
'🍏🍏🍋🍋🍊🍊🍓🍓'.replace(/🍏/g, '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'
Como desarrollador, es molesto tener que hacer esta conversión de cadena a expresión regular si lo único que realmente deseas es un reemplazo global de subcadenas. Más importante aún, esta conversión es propensa a errores y una fuente común de problemas. Considera el siguiente ejemplo:
const queryString = 'q=query+string+parameters';
queryString.replace('+', ' ');
// → 'q=query string+parameters' ❌
// Solo se reemplaza la primera aparición.
queryString.replace(/+/, ' ');
// → SyntaxError: expresión regular inválida ❌
// Resulta que `+` es un carácter especial dentro de los patrones de expresión regular.
queryString.replace(/\+/, ' ');
// → 'q=query string+parameters' ❌
// Escapar los caracteres especiales de expresión regular hace que la expresión sea válida, pero
// esto todavía reemplaza solo la primera aparición de `+` en la cadena.
queryString.replace(/\+/g, ' ');
// → 'q=query string parameters' ✅
// Escapar los caracteres especiales de expresión regular Y usar el indicador `g` hace que funcione.
Convertir un literal de cadena como '+'
en una expresión regular global no es solo cuestión de quitar las comillas '
, envolverlo en barras /
y agregar el indicador g
, sino que debemos escapar cualquier carácter que tenga un significado especial en las expresiones regulares. Esto es fácil de olvidar y difícil de realizar correctamente, ya que JavaScript no ofrece un mecanismo incorporado para escapar patrones de expresión regular.
Una alternativa es combinar String#split
con Array#join
:
const queryString = 'q=query+string+parameters';
queryString.split('+').join(' ');
// → 'q=query string parameters'
Este enfoque evita cualquier escape, pero implica el trabajo adicional de dividir la cadena en una matriz de partes solo para volver a unirla.
Claramente, ninguna de estas alternativas es ideal. ¿No sería genial si una operación básica como el reemplazo global de subcadenas fuera sencilla en JavaScript?
String.prototype.replaceAll
El nuevo método String#replaceAll
resuelve estos problemas y proporciona un mecanismo directo para realizar reemplazos globales de subcadenas:
'aabbcc'.replaceAll('b', '_');
// → 'aa__cc'
'🍏🍏🍋🍋🍊🍊🍓🍓'.replaceAll('🍏', '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'
const queryString = 'q=query+string+parameters';
queryString.replaceAll('+', ' ');
// → 'q=query string parameters'
Para mantener la consistencia con las API preexistentes en el lenguaje, String.prototype.replaceAll(searchValue, replacement)
se comporta exactamente como String.prototype.replace(searchValue, replacement)
, con las siguientes dos excepciones:
- Si
searchValue
es una cadena, entoncesString#replace
solo reemplaza la primera aparición de la subcadena, mientras queString#replaceAll
reemplaza todas las ocurrencias. - Si
searchValue
es una expresión regular no global, entoncesString#replace
reemplaza solo una coincidencia, similar a cómo se comporta con cadenas. Por otro lado,String#replaceAll
lanza una excepción en este caso, ya que probablemente sea un error: si realmente deseas "reemplazar todas" las coincidencias, usarías una expresión regular global; si solo deseas reemplazar una única coincidencia, puedes usarString#replace
.
La pieza importante de la nueva funcionalidad radica en ese primer punto. String.prototype.replaceAll
enriquece JavaScript con soporte de primera clase para reemplazos globales de subcadenas, sin necesidad de expresiones regulares ni alternativas.
Una nota sobre los patrones especiales de reemplazo
Vale la pena mencionar: tanto replace
como replaceAll
admiten patrones especiales de reemplazo. Aunque estos son más útiles en combinación con expresiones regulares, algunos de ellos ($$
, $&
, $`
, y $'
) también tienen efecto al realizar un reemplazo de cadena simple, lo cual puede ser sorprendente:
'xyz'.replaceAll('y', '$$');
// → 'x$z' (no 'x$$z')
En caso de que tu cadena de reemplazo contenga uno de estos patrones, y desees usarlos tal cual, puedes desactivar el comportamiento mágico de sustitución utilizando una función reemplazadora que devuelva la cadena:
'xyz'.replaceAll('y', () => '$$');
// → 'x$$z'