¿JavaScript utiliza cadenas inmutables o mutables? ¿Necesito un "generador de cadenas"?
¿JavaScript utiliza cadenas inmutables o mutables? ¿Necesito un "generador de cadenas"?
Respuestas:
Son inmutables. No puede cambiar un carácter dentro de una cadena con algo como var myString = "abbdef"; myString[2] = 'c'
. Los métodos de manipulación de cadenas, como trim
, slice
devuelven nuevas cadenas.
Del mismo modo, si tiene dos referencias a la misma cadena, modificar una no afecta a la otra.
let a = b = "hello";
a = a + " world";
// b is not affected
Sin embargo, siempre escuché lo que Ash mencionó en su respuesta (que usar Array.join es más rápido para la concatenación), así que quería probar los diferentes métodos para concatenar cadenas y abstraer la forma más rápida en un StringBuilder. Escribí algunas pruebas para ver si esto es cierto (¡no lo es!).
Esto era lo que creía que sería la forma más rápida, aunque seguía pensando que agregar una llamada al método podría hacerlo más lento ...
function StringBuilder() {
this._array = [];
this._index = 0;
}
StringBuilder.prototype.append = function (str) {
this._array[this._index] = str;
this._index++;
}
StringBuilder.prototype.toString = function () {
return this._array.join('');
}
Aquí hay pruebas de velocidad de rendimiento. Los tres crean una cadena gigantesca compuesta de concatenación "Hello diggity dog"
cien mil veces en una cadena vacía.
He creado tres tipos de pruebas
Array.push
yArray.join
Array.push
, luego usarArray.join
Entonces creé los mismos tres pruebas mediante la abstracción de ellas en StringBuilderConcat
, StringBuilderArrayPush
y StringBuilderArrayIndex
http://jsperf.com/string-concat-without-sringbuilder/5 Por favor, ir allí y ejecutar pruebas para que podamos obtener una muestra agradable. Tenga en cuenta que solucioné un pequeño error, por lo que los datos de las pruebas se borraron, actualizaré la tabla una vez que haya suficientes datos de rendimiento. Vaya a http://jsperf.com/string-concat-without-sringbuilder/5 para ver la tabla de datos anterior.
Aquí hay algunos números (última actualización en Ma5rch 2018), si no desea seguir el enlace. El número en cada prueba está en 1000 operaciones / segundo ( más alto es mejor )
| Browser | Index | Push | Concat | SBIndex | SBPush | SBConcat |
---------------------------------------------------------------------------
| Chrome 71.0.3578 | 988 | 1006 | 2902 | 963 | 1008 | 2902 |
| Firefox 65 | 1979 | 1902 | 2197 | 1917 | 1873 | 1953 |
| Edge | 593 | 373 | 952 | 361 | 415 | 444 |
| Exploder 11 | 655 | 532 | 761 | 537 | 567 | 387 |
| Opera 58.0.3135 | 1135 | 1200 | 4357 | 1137 | 1188 | 4294 |
Recomendaciones
Hoy en día, todos los navegadores de hoja perenne manejan bien la concatenación de cadenas. Array.join
solo ayuda a IE 11
En general, Opera es más rápido, 4 veces más rápido que Array.
Firefox es el segundo y Array.join
es solo un poco más lento en FF pero considerablemente más lento (3x) en Chrome.
Chrome es tercero, pero el concat de cadena es 3 veces más rápido que Array.join
Crear un StringBuilder parece no afectar demasiado el rendimiento.
Espero que alguien más encuentre esto útil
Caso de prueba diferente
Como @RoyTinker pensó que mi prueba era defectuosa, creé un nuevo caso que no crea una cadena grande al concatenar la misma cadena, utiliza un carácter diferente para cada iteración. La concatenación de cuerdas todavía parecía más rápida o igual de rápida. Hagamos que esas pruebas se ejecuten.
Sugiero que todos sigan pensando en otras formas de probar esto, y siéntanse libres de agregar nuevos enlaces a los diferentes casos de prueba a continuación.
join
concatenación de cadenas versus, por lo tanto, construir la matriz antes de la prueba. No creo que sea trampa si se entiende ese objetivo (y join
enumera la matriz internamente, por lo que tampoco es trampa omitir un for
bucle de la join
prueba).
Array.join
del libro de rinocerontes :
En JavaScript, las cadenas son objetos inmutables, lo que significa que los caracteres dentro de ellos no se pueden cambiar y que cualquier operación en las cadenas realmente crea nuevas cadenas. Las cadenas se asignan por referencia, no por valor. En general, cuando un objeto se asigna por referencia, un cambio realizado en el objeto a través de una referencia será visible a través de todas las demás referencias al objeto. Sin embargo, debido a que las cadenas no se pueden cambiar, puede tener múltiples referencias a un objeto de cadena y no preocuparse de que el valor de la cadena cambie sin que usted lo sepa.
null
undefined
number
y boolean
. Las cadenas se asignan por valor y no por referencia y se pasan como tales. Por lo tanto, las cadenas no son solo inmutables, son un valor . Cambiar la cadena "hello"
a ser "world"
es como decidir que de ahora en adelante el número 3 es el número 4 ... no tiene sentido.
var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
String
objetos creados con el constructor de cadenas son envoltorios alrededor de los valores de cadena de JavaScript. Puede acceder al valor de cadena del tipo en caja utilizando la .valueOf()
función; esto también es cierto para los Number
objetos y los valores numéricos. Es importante tener en cuenta que los String
objetos creados usando new String
no son cadenas reales, sino envoltorios o cajas alrededor de las cadenas. Ver es5.github.io/#x15.5.2.1 . Sobre cómo las cosas se convierten en objetos, consulte es5.github.io/#x9.9
Consejo de rendimiento:
Si tiene que concatenar cadenas grandes, coloque las partes de la cadena en una matriz y use el Array.Join()
método para obtener la cadena general. Esto puede ser muchas veces más rápido para concatenar una gran cantidad de cadenas.
No hay StringBuilder
en JavaScript.
Solo para aclarar para mentes simples como la mía (de MDN ):
Los inmutables son los objetos cuyo estado no se puede cambiar una vez que se crea el objeto.
La cadena y los números son inmutables.
Inmutable significa que:
Puede hacer que un nombre de variable señale un nuevo valor, pero el valor anterior aún se conserva en la memoria. De ahí la necesidad de recolección de basura.
var immutableString = "Hello";
// En el código anterior, se crea un nuevo objeto con valor de cadena.
immutableString = immutableString + "World";
// Ahora estamos agregando "Mundo" al valor existente.
Parece que estamos mutando la cadena 'immutableString', pero no lo estamos. En lugar:
Al agregar "immutableString" con un valor de cadena, se producen los siguientes eventos:
- Se recupera el valor existente de "immutableString"
- "Mundo" se agrega al valor existente de "immutableString"
- El valor resultante se asigna a un nuevo bloque de memoria.
- El objeto "immutableString" ahora apunta al espacio de memoria recién creado
- El espacio de memoria creado anteriormente está ahora disponible para la recolección de basura.
El valor del tipo de cadena es inmutable, pero el objeto String, que se crea utilizando el constructor String (), es mutable, porque es un objeto y puede agregarle nuevas propiedades.
> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }
Mientras tanto, aunque puede agregar nuevas propiedades, no puede cambiar las propiedades ya existentes
Una captura de pantalla de una prueba en la consola de Chrome
En conclusión, 1. todo el valor del tipo de cadena (tipo primitivo) es inmutable. 2. El objeto String es mutable, pero el valor del tipo de cadena (tipo primitivo) que contiene es inmutable.
new String
genera un envoltorio mutable alrededor de una cadena inmutable
String
objeto (contenedor), lo que significa que no es inmutable (de forma predeterminada; como cualquier otro objeto que pueda invocar Object.freeze
para que sea inmutable). Pero un tipo de valor de cadena primitivo, ya sea contenido en un String
contenedor de objetos o no, siempre es inmutable.
Con respecto a su pregunta (en su comentario a la respuesta de Ash) sobre el StringBuilder en ASP.NET Ajax, los expertos parecen estar en desacuerdo sobre este.
Christian Wenz dice en su libro Programming ASP.NET AJAX (O'Reilly) que "este enfoque no tiene ningún efecto medible en la memoria (de hecho, la implementación parece ser un poco más lenta que el enfoque estándar)".
Por otro lado, Gallo et al dicen en su libro ASP.NET AJAX in Action (Manning) que "cuando el número de cadenas para concatenar es mayor, el generador de cadenas se convierte en un objeto esencial para evitar grandes caídas de rendimiento".
Supongo que tendrías que hacer tu propia evaluación comparativa y los resultados también pueden diferir entre los navegadores. Sin embargo, incluso si no mejora el rendimiento, podría considerarse "útil" para los programadores que están acostumbrados a codificar con StringBuilders en lenguajes como C # o Java.
Es una publicación tardía, pero no encontré una buena cita entre las respuestas.
Aquí hay un definitivo, excepto de un libro confiable:
Las cadenas son inmutables en ECMAScript, lo que significa que una vez que se crean, sus valores no pueden cambiar. Para cambiar la cadena contenida en una variable, la cadena original debe destruirse y la variable debe llenarse con otra cadena que contenga un nuevo valor ... — JavaScript profesional para desarrolladores web, 3ª ed., P.43
Ahora, la respuesta que cita el extracto del libro de Rhino es correcta sobre la inmutabilidad de las cadenas, pero es incorrecta al decir "Las cadenas se asignan por referencia, no por valor". (Probablemente, originalmente tenían la intención de poner las palabras de manera opuesta).
El concepto erróneo de "referencia / valor" se aclara en el capítulo "JavaScript profesional", denominado "Valores primitivos y de referencia":
Los cinco tipos primitivos ... [son]: Indefinido, Nulo, Booleano, Número y Cadena. Se dice que se accede a estas variables por valor, porque está manipulando el valor real almacenado en la variable. — JavaScript profesional para desarrolladores web, 3ª ed., P.85
eso se opone a los objetos :
Cuando manipulas un objeto, realmente estás trabajando en una referencia a ese objeto en lugar del objeto real en sí. Por esta razón, se dice que se accede a dichos valores por referencia. — JavaScript profesional para desarrolladores web, 3ª ed., P.85
Las cadenas de JavaScript son de hecho inmutables.
Las cadenas en Javascript son inmutables