2011-04-29 8 views
13

Así que básicamente me escribió a mí mismo esta función con el fin de ser capaz de contar el número de ocurrencias de una subcadena en una cadena:Cambiar las banderas RegExp

String.prototype.numberOf = function(needle) { 
    var num = 0, 
     lastIndex = 0; 
    if(typeof needle === "string" || needle instanceof String) { 
    while((lastIndex = this.indexOf(needle, lastIndex) + 1) > 0) 
     {num++;} return num; 
    } else if(needle instanceof RegExp) { 
    // needle.global = true; 
    return this.match(needle).length; 
    } return 0; 
}; 

El método en sí mismo lleva a cabo bastante bien y tanto la expresión regular y Las búsquedas basadas en cadenas son bastante comparables en cuanto al tiempo de ejecución (tanto ~ 2ms en todo el amplio "451 Fahrenheit" de Ray Bradbury buscando todos los "s").

Lo que me molesta, sin embargo, es la imposibilidad de cambiar el indicador de la instancia RegExp suministrada. No tiene sentido llamar al String.prototype.match en esta función sin que el indicador global de la Expresión regular suministrado se establezca en verdadero, ya que solo notará la primera aparición luego. Sin duda, podría establecer el indicador manualmente en cada RegExp pasado a la función, sin embargo, preferiría poder clonar y luego manipular los indicadores de Expresión regular suministrados.

Sorprendentemente, no estoy autorizado a hacerlo ya que el bandera RegExp.prototype.global (más precisamente todos los indicadores) parecen ser de solo lectura. Desde allí la línea comentada 8.

Así que mi pregunta es: ¿Existe un agradable manera de cambiar las banderas de un objeto RegExp?

Realmente no quiero hacer cosas como esta:

if(!expression.global) 
    expression = eval(expression.toString() + "g"); 

Algunas implementaciones podrían no apoyar el evento RegExp.prototype.toString y simplemente heredan de la Object.prototype, o podría ser un formato diferente por completo. Y para empezar, parece una mala práctica de codificación.

+0

veo. Bueno, edité la publicación, por lo que puedes eliminar la velocidad de descenso. :-) – Witiko

+0

Hecho y hecho. ¡Lo siento por eso! – ridgerunner

Respuesta

12

En primer lugar, el código actual no funciona correctamente cuando needle es una expresión regular que no coincide. es decir, la línea siguiente:

return this.match(needle).length; 

El método match devuelve null cuando no hay ninguna coincidencia. Se genera un error de JavaScript cuando se accede (sin éxito) a la propiedad length de null. Esto se puede arreglar fácilmente de esta manera:

var m = this.match(needle); 
return m ? m.length : 0; 

Ahora para el problema en cuestión. Tiene razón cuando dice que global, ignoreCase y multiline son propiedades de solo lectura. La única opción es crear un nuevo RegExp.Esto se hace fácilmente ya que la cadena fuente de expresiones regulares se almacena en la propiedad re.source. Aquí es una versión modificada de la prueba de su función que corrige el problema anterior y crea un nuevo objeto RegExp cuando needle no tiene ya su conjunto de global bandera:

String.prototype.numberOf = function(needle) { 
    var num = 0, 
    lastIndex = 0; 
    if (typeof needle === "string" || needle instanceof String) { 
     while((lastIndex = this.indexOf(needle, lastIndex) + 1) > 0) 
      {num++;} return num; 
    } else if(needle instanceof RegExp) { 
     if (!needle.global) { 
      // If global flag not set, create new one. 
      var flags = "g"; 
      if (needle.ignoreCase) flags += "i"; 
      if (needle.multiline) flags += "m"; 
      needle = RegExp(needle.source, flags); 
     } 
     var m = this.match(needle); 
     return m ? m.length : 0; 
    } 
    return 0; 
}; 
+0

Gracias por señalar la incoherencia. Me gusta la solución, quizás sea más seguro no extender el prototipo RegExp con una función de indicadores (como se sugirió anteriormente). – Witiko

+0

Mejor aún, use 'myRegex.test (str)' si eso es todo lo que le importa. Es más corto y más rápido. – Phrogz

+0

No del todo, estoy tratando de contar todas las ocurrencias. :-) – Witiko

8
var globalRegex = new RegExp(needle.source, "g"); 

Live DemoEDITAR: El m era sólo por el bien de demostrar que se pueden establecer varios modificadores

var regex = /find/; 
var other = new RegExp(regex.source, "gm"); 
alert(other.global); 
alert(other.multiline); 
4

No hay mucho que puede hacer, pero le recomiendo que evite usando eval. Puede ampliar el prototipo RegExp para que lo ayude.

RegExp.prototype.flags = function() { 
    return (this.ignoreCase ? "i" : "") 
     + (this.multiline ? "m" : "") 
     + (this.global ? "g" : ""); 
}; 

var reg1 = /AAA/i; 
var reg2 = new RegExp(reg1.source, reg1.flags() + 'g'); 
+0

Descubrí la parte .source hace unos momentos. Sin embargo, me olvidé completamente del hecho de que las banderas no son parte de la fuente. :) Creo que incorporaré la idea. Gracias;) – Witiko