¿Las cadenas de JavaScript son inmutables? ¿Necesito un "generador de cadenas" en JavaScript?

Respuestas:

294

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, slicedevuelven 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

  • Usando Array.pushyArray.join
  • Usar la indexación de matriz para evitar Array.push, luego usarArray.join
  • Concatenación de cuerdas rectas

Entonces creé los mismos tres pruebas mediante la abstracción de ellas en StringBuilderConcat, StringBuilderArrayPushy 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.joinsolo 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.joines 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.

http://jsperf.com/string-concat-without-sringbuilder/7

Juan mendes
fuente
@Juan, el enlace que nos solicitó visitar concatena una cadena de 112 caracteres 30 veces. Aquí hay otra prueba que podría ayudar a equilibrar las cosas: Array.join vs concatenación de cadenas en 20,000 cadenas diferentes de 1 carácter (la unión es mucho más rápida en IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker
1
@RoyTinker Roy, oh Roy, tus pruebas son engañosas porque estás creando la matriz en la configuración de la prueba. Aquí está la prueba real con diferentes caracteres jsperf.com/string-concat-without-sringbuilder/7 Siéntase libre de crear nuevos casos de prueba, pero crear la matriz es parte de la prueba en sí misma
Juan Mendes
@JuanMendes Mi objetivo era reducir el caso de prueba a una comparación estricta de la joinconcatenació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 joinenumera la matriz internamente, por lo que tampoco es trampa omitir un forbucle de la joinprueba).
Roy Tinker
2
@RoyTinker Sí, cualquier generador de cadenas requerirá construir la matriz. La pregunta es sobre si se necesita un generador de cadenas. Si ya tiene las cadenas en una matriz, entonces no es un caso de prueba válido para lo que estamos discutiendo aquí
Juan Mendes
1
@Baltusaj Las columnas que dicen Index / Push están usandoArray.join
Juan Mendes
41

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.

menta
fuente
77
Enlace a una sección apropiada del libro de rinocerontes: books.google.com/…
baudtack
116
La cita del libro de Rhino (y por lo tanto esta respuesta) está mal aquí. En JavaScript, las cadenas son tipos de valores primitivos y no objetos (especificaciones) . De hecho, a partir de ES5, son uno de los únicos 5 tipos de valor junto con null undefined numbery 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.
Benjamin Gruenbaum
8
Sí, como dice mi comentario, las cadenas son inmutables, pero no son tipos de referencia ni objetos, son tipos de valores primitivos. Una manera fácil de ver que ninguno de los dos sería intentar agregar una propiedad a una cadena y luego leerlo:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum
99
@ VidarS.Ramdal No, los Stringobjetos 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 Numberobjetos y los valores numéricos. Es importante tener en cuenta que los Stringobjetos creados usando new Stringno 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
Benjamin Gruenbaum
55
En cuanto a por qué algunas personas dicen que las cadenas son objetos, probablemente provengan de Python o Lisp o de cualquier otro lenguaje donde su especificación use la palabra "objeto" para referirse a cualquier tipo de dato (incluso enteros). Solo necesitan leer cómo la especificación ECMA define la palabra: "miembro del tipo Object". Además, incluso la palabra "valor" puede significar cosas diferentes según las especificaciones de diferentes idiomas.
Jisang Yoo
21

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 StringBuilderen JavaScript.

Ceniza
fuente
Sé que no hay un stringBuilder, msAjax tiene uno, y solo estaba pensando si es útil o no
DevelopingChris
55
¿Qué tiene esto que ver con que las cadenas sean inmutables o no?
baudtack
44
@docgnome: Debido a que las cadenas son inmutables, la concatenación de cadenas requiere crear más objetos que el enfoque Array.join
Juan Mendes
8
Según la prueba anterior de Juan, la concatenación de cadenas es en realidad más rápida tanto en IE como en Chrome, mientras que es más lenta en Firefox.
Bill Yang
99
Considere actualizar su respuesta, puede haber sido cierto hace mucho tiempo, pero ya no lo es. Ver jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes
8

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:

  1. Se recupera el valor existente de "immutableString"
  2. "Mundo" se agrega al valor existente de "immutableString"
  3. El valor resultante se asigna a un nuevo bloque de memoria.
  4. El objeto "immutableString" ahora apunta al espacio de memoria recién creado
  5. El espacio de memoria creado anteriormente está ahora disponible para la recolección de basura.
Katinka Hesselink
fuente
4

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.

zhanziyang
fuente
Los objetos de la cadena Javascript son inmutables developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun
@prasun pero en esa página dice: "Todos los tipos, excepto los objetos, definen valores inmutables (valores que no pueden modificarse)". Los objetos de cadena son objeto. ¿Y cómo es inmutable si puede agregarle nuevas propiedades?
zhanziyang
lea la sección "Tipo de cadena". El enlace de la cadena de Javascript apunta tanto a primitivo como a objeto y luego dice "las cadenas de JavaScript son inmutables". Parece que el documento no es claro sobre este tema, ya que entra en conflicto con dos notas diferentes
prasun
66
new Stringgenera un envoltorio mutable alrededor de una cadena inmutable
tomdemuyt
1
Es muy fácil de probar ejecutando el código de @ zhanziyang arriba. Puede agregar totalmente nuevas propiedades a un Stringobjeto (contenedor), lo que significa que no es inmutable (de forma predeterminada; como cualquier otro objeto que pueda invocar Object.freezepara que sea inmutable). Pero un tipo de valor de cadena primitivo, ya sea contenido en un Stringcontenedor de objetos o no, siempre es inmutable.
Mark Reed
3

Las cadenas son inmutables : no pueden cambiar, solo podemos hacer nuevas cadenas.

Ejemplo:

var str= "Immutable value"; // it is immutable

var other= statement.slice(2, 10); // new string
GibboK
fuente
1

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.

BirgerH
fuente
1

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

revelt
fuente
FWIW: El libro de Rhino probablemente significa que internamente / implementación una asignación de cadena está almacenando / copiando un puntero (en lugar de copiar el contenido de la cadena). No parece un accidente de su parte, según el texto que sigue. Pero estoy de acuerdo: hacen un mal uso del término "por referencia". No es "por referencia" solo porque la implementación pasa punteros (por rendimiento). Wiki: la estrategia de evaluación es una lectura interesante sobre este tema.
ToolmakerSteve
-2

Las cadenas en Javascript son inmutables

Glenn Slaven
fuente