¿Por qué hay que evitar un loop infinito con expresiones regulares incrementando RegExp.lastIndex? – javascript regex

Pregunta:


Esta es la forma recomendada de obtener todas las coincidencias y capturas de una expresión regular en JavaScript, en donde el resultado puede ser una cadena vacía:

const texto = "ab-cde",
      regex = /(?=(w{2}))/g;
let   match;

while ((match = regex.exec(texto)) !== null) {
    if (match.index === regex.lastIndex) { //evitar loop infinito
        regex.lastIndex++;
    }
    console.log('Texto: "' + match[0] + '"', 'tPosición:', match.index);
    console.log(' -Grupo 1: "' + match[1] + '"');
}
  1. ¿Por qué hay que incrementar RegExp.lastIndex para evitar un loop infinito?
  2. ¿Cuando hay que usarlo? ¿Sólo ocurre con RegExp.exec() usando el modificador /g?
  3. ¿Es exclusivo de JavaScript o también ocurre con otros dialectos de regex?
  4. ¿Se comporta de esta manera en todos los navegadores?
  5. ¿Es un bug o está especificado así por diseño?
Preguntado por: Mariano

Mariano

¿Por qué es necesario evitar un loop infinito?

La condición es para ver si el inicio de la coincidencia (match.index) es la misma posición que donde se iniciará el siguiente intento (regex.lastIndex). En caso de que sea la misma, se incrementa en 1 la posición.

if (match.index === regex.lastIndex) { //evitar loop infinito
    regex.lastIndex++;
}

Esto se debe a que, como la coincidencia es en realidad de largo cero, ya que la inspección no consume caracteres, y el resultado del método realmente es una cadena vacía (""), la posición desde donde se intenta el regex no avanza ninguna posición. Por eso, se modifica manualmente para que no siga intentando coincidir desde la misma posición, entrando en un loop infinito.

Si coincidió desde una posición, obviamente va a seguir coincidiendo desde esa misma posición de nuevo, y de nuevo, y de nuevo…

¿Cuándo hay que usarlo?

Esta condición es una buena práctica a utilizar en todos los casos en los que se utilice el modificador /g (global) y un bucle con RegExp.prototype.exec(). Obviamente no sucede con RegExp.test() ni con String.match(), donde no hay un control iterativo sobre cada uno de los intentos de coincidencia.

Además, no todos los navegadores se comportan igual. Incluso en expresiones que no deberían jamás devolver una cadena vacía, es una buena salvaguarda, y recomiendo utilizar siempre esta construcción.

¿Sólo en JavaScript? ¿En todos los navegadores?

JavaScript tiene una de las peores implementaciones de expresiones regulares dentro de los lenguajes comúnmente utilizados (ver más info). Todos los otros motores de expresiones regulares, luego de coincidir con una cadena vacía muestran uno de estos dos comportamientos:

Una nota de color es que versiones previas de IE incrementaban automáticamente
a .lastIndex luego de una coincidencia de largo cero con expresiones
globales.
En el artículo An IE lastIndex Bug with Zero-Length Regex Matches
se describe con más detalle, y aunque Steven Levithan lo menciona como “bug”,
en mi opinión era en realidad la forma correcta de realizarlo, más en
concordancia con lo que expone Jan Goyvaerts en
Watch Out for Zero-Length Matches.

Sin embargo, IE terminó dejando a .lastIndex sin incrementar para mostrar
el mismo comportamiento que la mayoría de los navegadores desde IE9
(sólo si se especifica un DOCTYPE de HTML 4.01 o HTML5) o en IE10+
(sin importar el DOCTYPE).

JavaScript tiene un error de concepción en no utilizar una de estas dos estrategias, rompe la norma del resto de los dialectos (flavors) de regex, y en ese sentido es un bug.

¿Bug o por diseño?

Si bien este comportamiento no es un “bugper se (RESOLVED INVALID en Bugzilla), utilicé el término porque es un concepto erróneo en la implementación de Oniguruma (el motor de RegExp) sobre este comportamiento.

El problema radica originalmente en el estándar. En ECMA-262 se define a regexp.lastIndex como “el índice en el String en donde iniciar el próximo intento de coincidencia” (en 21.2.6.1) (no dejen que el nombre los engañe, no es el final de la coincidencia), y luego:

La diferencia entre ambos métodos es inconsistente. Incluso, se reafirma en las notas de compatibilidad hacia atrás, pero sólo para String.prototype.match() y String.prototype.replace():

El comportamiento correcto es que lastIndex debe ser incrementado en 1, sólo si el patrón coincidió con una cadena vacía. (en D).

Y, si bien, sólo sucede con RegExp.prototype.exec(), este método es el único que aporta la información completa de una coincidencia (es el principal con regex). Por ejemplo, es la única forma de obtener el texto capturado por grupos en una expresión global.

Conclusiones

En el estándar queda claro que la falta de una definición consistente con el uso de expresiones regulares en cualquier otro lenguaje hace que sea necesario agregar está condición extra para incrementar el índice manualmente, algo totalmente ilógico e innecesario. Este IF es una buena práctica recomendada siempre que se haga un bucle de este estilo.

Fuente

Related Posts:

¿Cómo obtener la respuesta de una llamada asíncrona (AJAX) fuera de ella? – javascript jquery asincrónico
Pregunta: Actualmente tengo esta función: function devuelveButaca(posicion){ var array = posicion.split('_'); var row = array; var column ...
Evitar múltiples toggle con misma clase – javascript
Pregunta: Para un evento toggle con puro javascript lo desarrollo de la siguiente forma: Javascript // Esta clase lo expande. const expandButton = document.querySelector('.expand-button'); expandButton.addEventListener('click', ()=> { ...
Validación de datos nulos en una función – javascript
Pregunta: Tengo la siguiente función pero al momento validar todos mis datos me los registra todos a excepción del los "containers" que son grids y ...
Como poner una imagen de fondo a pantalla completa – javascript jquery html5
Pregunta: Como puedo hacer que la imagen abarque todo el tamaño de la pantalla. Se que de las propiedad min-width y min-heigth pero ellas no ...
Error de: Uncaught TypeError: url.indexOf is not a function por Jquery – javascript jquery
Pregunta: En el navegador, reviso en inspeccionar elemento, y me sale el siguiente error: Uncaught TypeError: url.indexOf is not a function Me imagino que es ...
Como validar array checkbox – javascript php jquery
Pregunta: Que tal buen dia, alguien me podria decir como se puede hacer para que cuando presione el boton "BAJAR DATOS" si este esta vacio ...
Uso de js y c# en unity. ¿Igual rendimiento? – javascript c# unity3d
Pregunta: Estoy comenzando con unity . Y veo que se pueden usar c# y js para programar. Mi pregunta es si tanto c# como js ...
Como cambiar una propiedad html dependiendo del valor de un perfil recogido de clase java – javascript jquery html
Pregunta: Tengo una caja de texto en la que me gustaría poder escribir o no dependiendo del perfil que utilice la aplicación. Esta caja de ...
Agregar marcador especifico a una localización Google Maps – javascript google-maps
Pregunta: ¿Cómo podría añadir un marcador personalizado en Google maps? Actualmente el código que tengo es el siguiente: var locations = [ ['Bondi Beach', -33.890542, ...
Usar variable en otra función con javascript – javascript html app
Pregunta: Primero que todo voy a dejar un enlace donde pueden ver el ejemplo funcionando de manera completa. Enlace a la pagina AQUI El valor elegido en ...
Ocultar y Mostrar con Mapa de Google – javascript c# asp.net
Pregunta: Tengo un div en mi página ASP.NET que contiene un Mapa de Google, y necesito mostrarlo y ocultarlo pero no me funciona. Este es el ...
Copiar valor a un input que esta dentro de un footer de un datatable – javascript jquery datatables
Pregunta: Estoy usando los DataTables de jQuery y me estoy encontrando con el siguiente problema: cuando pulso en un botón necesito pasarle un valor con ...
¿Cómo rellenar cada circulo al pasar el mouse encima? – javascript jquery html
Pregunta: ¡Hola! Estoy practicando jQuery, y me gustaría poder hacer como un ranking. A lo que quiero llegar es que cada vez que el mouse ...
convertir array a Map en javascript – javascript map nodejs
Pregunta: quiero convertir un arreglo a Map(), en este console "console.log(arrayParaMap);" sale undefined el ejemplo que uso es este //example {a:1,a:2,a:1,b:1,b:5,b:6} el map que prentendo obtener es ...
como iterar un objeto en nodejs – javascript nodejs
Pregunta: Hice esta función: const obj = ; const objMapped = obj.reduce((acc, item) => { ...

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *