Recorte de JavaScript más rápido

Dado que JavaScript no incluye un trim de forma nativa, está incluido en innumerables bibliotecas de JavaScript, generalmente como una función global o agregada a String.prototype . Sin embargo, nunca he visto una implementación que funcione tan bien como podría, probablemente porque la mayoría de los programadores no entienden en profundidad ni se preocupan por los problemas de eficiencia de las expresiones regulares.

Después de ver un trim particularmente malo implementación, decidí investigar un poco para encontrar el enfoque más eficiente. Antes de entrar en el análisis, aquí están los resultados:

Método Firefox 2 IE 6
recortar1 15ms <0,5 ms
recortar2 31ms <0,5 ms
recortar3 46ms 31ms
trim4 47ms 46ms
recortar5 156ms 1656ms
recortar6 172ms 2406ms
recortar7 172ms 1640ms
trim8 281ms <0,5 ms
recortar9 125ms 78ms
recortar10 <0,5 ms <0,5 ms
recortar11 <0,5 ms <0,5 ms

Nota 1: La comparación se basa en recortar la Carta Magna (más de 27.600 caracteres) con un poco de espacio en blanco inicial y final 20 veces en mi sistema personal. Sin embargo, los datos que está recortando pueden tener un gran impacto en el rendimiento, que se detalla a continuación.

Nota 2: trim4 y trim6 son los que se encuentran más comúnmente en las bibliotecas de JavaScript en la actualidad.

Nota 3: La mala implementación antes mencionada no está incluida en la comparación, pero se muestra más adelante.

El análisis

Aunque hay 11 filas en la tabla anterior, son solo las más notables (por varias razones) de unas 20 versiones que escribí y comparé con varios tipos de cadenas. El siguiente análisis se basa en pruebas en Firefox 2.0.0.4, aunque he señalado dónde hay diferencias importantes en IE6.

  1. return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    A fin de cuentas, este es probablemente el mejor enfoque general. Su ventaja de velocidad es más notable con cuerdas largas, cuando la eficiencia es importante. La velocidad se debe en gran medida a una serie de optimizaciones internas de los intérpretes de expresiones regulares de JavaScript que activan las dos expresiones regulares discretas aquí. Específicamente, la verificación previa del carácter requerido y comienzo del ancla de cadena optimizaciones, posiblemente entre otras.
  2. return str.replace(/^\s+/, '').replace(/\s+$/, '');
    Muy similar a trim1 (arriba), pero un poco más lento ya que no activa todas las mismas optimizaciones.
  3. return str.substring(Math.max(str.search(/\S/), 0), str.search(/\S\s*$/) + 1);
    Suele ser más rápido que los siguientes métodos, pero más lento que los dos anteriores. Su velocidad proviene del uso de búsquedas simples de índice de caracteres.
  4. return str.replace(/^\s+|\s+$/g, '');
    Este enfoque comúnmente pensado es sin duda el más utilizado en las bibliotecas de JavaScript en la actualidad. Por lo general, es la implementación más rápida del grupo solo cuando se trabaja con cadenas cortas que no incluyen espacios en blanco iniciales o finales. Esta pequeña ventaja se debe en parte a la discriminación del carácter inicial optimización que activa. Si bien este es un ejecutante relativamente decente, es más lento que los tres métodos anteriores cuando se trabaja con cadenas más largas, porque la alternancia de nivel superior evita una serie de optimizaciones que de otro modo podrían activarse.
  5. str = str.match(/\S+(?:\s+\S+)*/);
    return str ? str[0] : '';

    Este es generalmente el método más rápido cuando se trabaja con cadenas vacías o solo con espacios en blanco, debido a la verificación previa del carácter requerido optimización que activa. Nota:en IE6, esto puede ser bastante lento cuando se trabaja con cadenas más largas.
  6. return str.replace(/^\s*(\S*(\s+\S+)*)\s*$/, '$1');
    Este es un enfoque relativamente común, popularizado en parte por algunos JavaScripters líderes. Es similar en enfoque (pero inferior) a trim8 . No hay una buena razón para usar esto en JavaScript, especialmente porque puede ser muy lento en IE6.
  7. return str.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, '$1');
    Lo mismo que trim6 , pero un poco más rápido debido al uso de un grupo que no captura (que no funciona en IE 5.0 y versiones anteriores). Nuevamente, esto puede ser lento en IE6.
  8. return str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
    Esto utiliza un enfoque codicioso simple, de un solo paso. ¡En IE6, esto es increíblemente rápido! La diferencia de rendimiento indica que IE tiene una optimización superior para la cuantificación de tokens de "cualquier carácter".
  9. return str.replace(/^\s*([\S\s]*?)\s*$/, '$1');
    Este es generalmente el más rápido con cadenas muy cortas que contienen tanto caracteres que no son espacios como espacios en blanco en los bordes. Esta pequeña ventaja se debe al enfoque simple, perezoso y de un solo paso que utiliza. Me gusta trim8 , esto es significativamente más rápido en IE6 que en Firefox 2.

Dado que he visto la siguiente implementación adicional en una biblioteca, la incluiré aquí como advertencia:

return str.replace(/^\s*([\S\s]*)\b\s*$/, '$1');

Aunque el anterior es a veces el método más rápido cuando se trabaja con cadenas cortas que contienen tanto caracteres que no son espacios como espacios en blanco en los bordes, funciona muy mal con cadenas largas que contienen numerosos límites de palabras, y es terrible (!) con cadenas largas compuestas de nada más que espacios en blanco, ya que eso desencadena una cantidad exponencialmente creciente de retroceso. No usar.

Un final diferente

Hay dos métodos en la tabla en la parte superior de esta publicación que aún no se han cubierto. Para esos, he usado un enfoque no regex e híbrido.

Después de comparar y analizar todo lo anterior, me preguntaba cómo funcionaría una implementación que no usara expresiones regulares. Esto es lo que probé:

function trim10 (str) {
	var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
	for (var i = 0; i < str.length; i++) {
		if (whitespace.indexOf(str.charAt(i)) === -1) {
			str = str.substring(i);
			break;
		}
	}
	for (i = str.length - 1; i >= 0; i--) {
		if (whitespace.indexOf(str.charAt(i)) === -1) {
			str = str.substring(0, i + 1);
			break;
		}
	}
	return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
}

¿Cómo funciona eso? Bueno, con cadenas largas que no contienen demasiados espacios en blanco iniciales o finales, supera a la competencia (excepto contra trim1 /2 /8 en IE, que ya son increíblemente rápidos allí).

¿Eso significa que las expresiones regulares son lentas en Firefox? No, en absoluto. El problema aquí es que, aunque las expresiones regulares son muy adecuadas para recortar los espacios en blanco iniciales, además de la biblioteca .NET (que ofrece un modo un tanto misterioso de "coincidencia hacia atrás"), en realidad no proporcionan un método para saltar al final de una cadena sin siquiera considerar los caracteres anteriores. Sin embargo, el trim10 que no depende de expresiones regulares hace exactamente eso, con el segundo ciclo trabajando hacia atrás desde el final de la cadena hasta que encuentra un carácter que no es un espacio en blanco.

Sabiendo eso, ¿qué sucede si creamos una implementación híbrida que combina la eficiencia universal de una expresión regular para recortar los espacios en blanco iniciales con la velocidad del método alternativo para eliminar los caracteres finales?

function trim11 (str) {
	str = str.replace(/^\s+/, '');
	for (var i = str.length - 1; i >= 0; i--) {
		if (/\S/.test(str.charAt(i))) {
			str = str.substring(0, i + 1);
			break;
		}
	}
	return str;
}

Aunque el anterior es un poco más lento que trim10 con algunas cadenas, utiliza significativamente menos código y sigue siendo muy rápido. Además, con cadenas que contienen muchos espacios en blanco iniciales (lo que incluye cadenas que solo contienen espacios en blanco), es mucho más rápido que trim10 .

En conclusión...

Dado que las diferencias entre las implementaciones entre navegadores y cuando se usan con diferentes datos son complejas y matizadas (ninguno de ellos es más rápido que todos los demás con los datos que puede arrojar), aquí están mis recomendaciones generales para un trim método:

  • Utilice trim1 si desea una implementación de propósito general que sea rápida entre navegadores.
  • Usar trim11 si desea manejar cadenas largas excepcionalmente rápido en todos los navegadores.

Para probar todas las implementaciones anteriores por sí mismo, pruebe mi página de evaluación comparativa muy rudimentaria. El procesamiento en segundo plano puede causar que los resultados sean severamente sesgados, así que ejecute la prueba varias veces (independientemente de cuántas iteraciones especifique) y solo considere los resultados más rápidos (ya que promediar el costo de la interferencia en segundo plano no es muy esclarecedor).

Como nota final, aunque a algunas personas les gusta almacenar en caché las expresiones regulares (por ejemplo, usando variables globales) para que puedan usarse repetidamente sin volver a compilar, en mi opinión, esto no tiene mucho sentido para un trim método. Todas las expresiones regulares anteriores son tan simples que normalmente no tardan más de un nanosegundo en compilarse. Además, algunos navegadores almacenan automáticamente en caché las expresiones regulares usadas más recientemente, por lo que un bucle típico que usa trim y no contiene un montón de otras expresiones regulares, es posible que no encuentre la recompilación de todos modos.

Editar (2008-02-04): Poco después de publicar esto me di cuenta de trim10 /11 podría estar mejor escrito. Varias personas también han publicado versiones mejoradas en los comentarios. Esto es lo que uso ahora, que toma el trim11 enfoque híbrido de estilo:

function trim12 (str) {
	var	str = str.replace(/^\s\s*/, ''),
		ws = /\s/,
		i = str.length;
	while (ws.test(str.charAt(--i)));
	return str.slice(0, i + 1);
}

Nueva biblioteca: ¿Eres un maestro de expresiones regulares de JavaScript o quieres serlo? Entonces necesitas mi elegante biblioteca XRegExp . Agrega una nueva sintaxis de expresiones regulares (incluidas las propiedades Unicode y de captura con nombre); s , x y n banderas; poderosas utilidades de expresiones regulares; y corrige las molestas inconsistencias del navegador. ¡Compruébalo!