Le stringhe JavaScript sono immutabili? Ho bisogno di un "generatore di stringhe" in JavaScript?


237

JavaScript utilizza stringhe immutabili o mutabili? Ho bisogno di un "costruttore di stringhe"?


3
Sì, y sono immutabili e hai bisogno di un "costruttore di stringhe" di qualche tipo. Leggi questo blog.codeeffects.com/Article/String-Builder-In-Java-Script o questo codeproject.com/KB/scripting/stringbuilder.aspx
Kizz

2
Interessante, quegli esempi contraddicono le mie scoperte nella mia risposta.
Juan Mendes,

Risposte:


294

Sono immutabili. Non puoi cambiare un carattere all'interno di una stringa con qualcosa di simile var myString = "abbdef"; myString[2] = 'c'. I metodi di manipolazione delle stringhe come trim, slicerestituiscono nuove stringhe.

Allo stesso modo, se si hanno due riferimenti alla stessa stringa, la modifica di una non influisce sull'altra

let a = b = "hello";
a = a + " world";
// b is not affected

Tuttavia, ho sempre sentito ciò che Ash ha menzionato nella sua risposta (che l'utilizzo di Array.join è più veloce per la concatenazione), quindi ho voluto testare i diversi metodi di concatenazione di stringhe e di astrazione del modo più veloce in StringBuilder. Ho scritto alcuni test per vedere se questo è vero (non lo è!).

Questo era quello che credevo fosse il modo più veloce, anche se continuavo a pensare che l'aggiunta di una chiamata al metodo potesse rallentare ...

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('');
}

Ecco i test di velocità delle prestazioni. Tutti e tre creano una gigantesca stringa composta da concatenare "Hello diggity dog"centomila volte in una stringa vuota.

Ho creato tre tipi di test

  • Utilizzando Array.pusheArray.join
  • Utilizzo dell'indicizzazione di array per evitare Array.push, quindi utilizzoArray.join
  • Concatenazione di stringhe dritte

Quindi ho creato gli stessi tre test astrandoli in StringBuilderConcat, StringBuilderArrayPushe StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Per favore, vai lì ed esegui i test in modo che possiamo ottenere un bel campione. Nota che ho corretto un piccolo bug, quindi i dati per i test sono stati cancellati, aggiornerò la tabella una volta che ci saranno abbastanza dati sulle prestazioni. Vai su http://jsperf.com/string-concat-without-sringbuilder/5 per la vecchia tabella di dati.

Ecco alcuni numeri (ultimo aggiornamento di Ma5rch 2018), se non vuoi seguire il link. Il numero su ogni test è in 1000 operazioni / secondo ( più alto è meglio )

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

I risultati

  • Al giorno d'oggi, tutti i browser sempreverdi gestiscono bene la concatenazione di stringhe. Array.joinaiuta solo IE 11

  • Nel complesso, Opera è la più veloce, 4 volte più veloce di Array.join

  • Firefox è secondo ed Array.joinè solo leggermente più lento in FF ma considerevolmente più lento (3x) in Chrome.

  • Chrome è terzo, ma il concat per stringhe è 3 volte più veloce di Array.join

  • La creazione di StringBuilder sembra non influire troppo sulle prestazioni.

Spero che qualcun altro lo trovi utile

Caso di prova diverso

Poiché @RoyTinker pensava che il mio test fosse imperfetto, ho creato un nuovo caso che non crea una grande stringa concatenando la stessa stringa, ma utilizza un carattere diverso per ogni iterazione. La concatenazione delle stringhe sembrava ancora più veloce o altrettanto veloce. Facciamo quei test in esecuzione.

Suggerisco a tutti di continuare a pensare ad altri modi per testarlo e di sentirsi liberi di aggiungere nuovi collegamenti a diversi casi di test di seguito.

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


@Juan, il link che ci hai chiesto di visitare concatena 30 volte una stringa da 112 caratteri. Ecco un altro test che potrebbe aiutare a bilanciare le cose: Array.join vs concatenazione di stringhe su 20.000 diverse stringhe da 1 carattere (il join è molto più veloce su IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@RoyTinker Roy, oh Roy, i tuoi test stanno tradendo perché stai creando l'array nella configurazione del test. Ecco il vero test usando diversi personaggi jsperf.com/string-concat-without-sringbuilder/7 Sentiti libero di creare nuovi casi di test, ma la creazione dell'array fa parte del test stesso
Juan Mendes

@JuanMendes Il mio obiettivo era restringere il caso di test a un rigoroso confronto tra joinconcatenazione di stringhe vs, quindi costruendo l'array prima del test. Non penso che sia barare se questo obiettivo è compreso (ed joinenumera l'array internamente, quindi non è barare anche per omettere un forloop dal jointest).
Roy Tinker,

2
@RoyTinker Sì, qualsiasi generatore di stringhe richiederà la creazione dell'array. La domanda è se è necessario un generatore di stringhe. Se hai già le stringhe in un array, non è un caso di prova valido per quello di cui stiamo discutendo qui
Juan Mendes,

1
@Baltusaj Le colonne che dicono che Index / Push stanno usandoArray.join
Juan Mendes,

41

dal libro del rinoceronte :

In JavaScript, le stringhe sono oggetti immutabili, il che significa che i caratteri al loro interno non possono essere modificati e che qualsiasi operazione sulle stringhe crea effettivamente nuove stringhe. Le stringhe sono assegnate per riferimento, non per valore. In generale, quando un oggetto viene assegnato per riferimento, una modifica apportata all'oggetto tramite un riferimento sarà visibile attraverso tutti gli altri riferimenti all'oggetto. Poiché le stringhe non possono essere modificate, tuttavia, puoi avere più riferimenti a un oggetto stringa e non preoccuparti che il valore della stringa cambi senza che tu lo sappia


7
Link a una sezione appropriata del libro sul rinoceronte: books.google.com/…
baudtack,

116
La citazione del libro di Rhino (e quindi questa risposta) è sbagliata qui. Nelle stringhe JavaScript sono tipi di valore primitivi e non oggetti (spec) . In effetti, a partire da ES5, sono uno dei soli 5 tipi di valore a fianco di null undefined numbere boolean. Le stringhe sono assegnate per valore e non per riferimento e vengono passate come tali. Pertanto, le stringhe non sono solo immutabili, sono un valore . Cambiare la stringa "hello"per essere "world"è come decidere che da ora in poi il numero 3 è il numero 4 ... non ha senso.
Benjamin Gruenbaum,

8
Sì, come dice il mio commento, le stringhe sono immutabili, ma non sono tipi di riferimento né oggetti, sono tipi di valore primitivi. Un modo semplice per vedere che non sono né quello di provare ad aggiungere una proprietà a una stringa e poi a leggerlo:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum,

9
@ VidarS.Ramdal No, gli Stringoggetti creati utilizzando il costruttore di stringhe sono wrapper attorno ai valori di stringa JavaScript. Puoi accedere al valore di stringa del tipo boxed usando la .valueOf()funzione - questo vale anche per Numberoggetti e valori numerici. È importante notare che gli Stringoggetti creati usando new Stringnon sono stringhe effettive ma sono wrapper o caselle attorno alle stringhe. Vedi es5.github.io/#x15.5.2.1 . Informazioni su come le cose si convertono in oggetti vedi es5.github.io/#x9.9
Benjamin Gruenbaum

5
Per quanto riguarda il motivo per cui alcune persone dicono che le stringhe sono oggetti, probabilmente provengono da Python o Lisp o da qualsiasi altro linguaggio in cui le sue specifiche usano la parola "oggetto" per indicare qualsiasi tipo di dato (anche numeri interi). Devono solo leggere come la specifica ECMA definisce la parola: "membro del tipo Object". Inoltre, anche la parola "valore" può significare cose diverse in base alle specifiche di lingue diverse.
Jisang Yoo,

21

Suggerimento sulle prestazioni:

Se è necessario concatenare stringhe di grandi dimensioni, inserire le parti di stringa in un array e utilizzare il Array.Join()metodo per ottenere la stringa complessiva. Questo può essere molte volte più veloce per concatenare un gran numero di stringhe.

Non c'è StringBuilderin JavaScript.


So che non esiste un stringBuilder, msAjax ne ha uno, e stavo solo riflettendo se fosse utile o meno
DevelopingChris

5
Cosa c'entra questo con le stringhe che sono immutabili o no?
baudtack,

4
@docgnome: poiché le stringhe sono immutabili, la concatenazione di stringhe richiede la creazione di più oggetti rispetto all'approccio Array.join
Juan Mendes,

8
Secondo il test di Juan sopra, la concatenazione di stringhe è in realtà più veloce sia in IE che in Chrome, mentre più lenta in Firefox.
Bill Yang,

9
Considera di aggiornare la tua risposta, potrebbe essere vero molto tempo fa, ma non lo è più. Vedi jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes,

8

Giusto per chiarire menti semplici come la mia (da MDN ):

Immutabili sono gli oggetti il ​​cui stato non può essere modificato una volta creato l'oggetto.

String e numeri sono immutabili.

Immutabile significa che:

È possibile fare in modo che un nome di variabile punti a un nuovo valore, ma il valore precedente rimane comunque in memoria. Da qui la necessità di raccolta dei rifiuti.

var immutableString = "Hello";

// Nel codice sopra, viene creato un nuovo oggetto con valore stringa.

immutableString = immutableString + "World";

// Ora stiamo aggiungendo "Mondo" al valore esistente.

Sembra che stiamo mutando la stringa 'immutableString', ma non lo siamo. Anziché:

Quando si aggiunge "immutableString" con un valore stringa, si verificano i seguenti eventi:

  1. Viene recuperato il valore esistente di "immutableString"
  2. "World" è aggiunto al valore esistente di "immutableString"
  3. Il valore risultante viene quindi assegnato a un nuovo blocco di memoria
  4. L'oggetto "immutableString" ora punta allo spazio di memoria appena creato
  5. Lo spazio di memoria creato in precedenza è ora disponibile per la garbage collection.

4

Il valore del tipo di stringa è immutabile, ma l'oggetto String, creato utilizzando il costruttore String (), è mutabile, poiché è un oggetto ed è possibile aggiungere nuove proprietà ad esso.

> var str = new String("test")
undefined
> str
[String: 'test']
> str.newProp = "some value"
'some value'
> str
{ [String: 'test'] newProp: 'some value' }

Nel frattempo, sebbene sia possibile aggiungere nuove proprietà, non è possibile modificare le proprietà già esistenti

Uno screenshot di un test nella console di Chrome

In conclusione, 1. tutto il valore del tipo di stringa (tipo primitivo) è immutabile. 2. L'oggetto String è modificabile, ma il valore del tipo di stringa (tipo primitivo) che contiene è immutabile.


Gli oggetti String di Javascript sono immutabili developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun

@prasun ma in quella pagina dice: "Tutti i tipi tranne gli oggetti definiscono valori immutabili (valori che non possono essere modificati)." Gli oggetti stringa sono oggetti. E come è immutabile se puoi aggiungere nuove proprietà su di esso?
Zhanziyang,

leggi la sezione "Tipo di stringa". Il collegamento String di Javascript punta sia alla primitiva che all'Oggetto e successivamente dice "Le stringhe JavaScript sono immutabili". Sembra che il documento non sia chiaro su questo argomento in quanto è in conflitto in due note diverse
prasun

6
new Stringgenera un wrapper mutabile attorno a una stringa immutabile
tomdemuyt,

1
È molto facile testare eseguendo il codice di @ zhanziyang sopra. Puoi aggiungere totalmente nuove proprietà a un Stringoggetto (wrapper), il che significa che non è immutabile (per impostazione predefinita; come qualsiasi altro oggetto puoi chiamarlo Object.freezeper renderlo immutabile). Ma un tipo di valore stringa primitivo, contenuto Stringo meno in un wrapper di oggetti, è sempre immutabile.
Mark Reed,

3

Le stringhe sono immutabili : non possono cambiare, possiamo solo creare nuove stringhe.

Esempio:

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

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

1

Per quanto riguarda la tua domanda (nel tuo commento alla risposta di Ash) sul StringBuilder in ASP.NET Ajax, gli esperti sembrano non essere d'accordo su questo.

Christian Wenz dice nel suo libro Programmazione ASP.NET AJAX (O'Reilly) che "questo approccio non ha alcun effetto misurabile sulla memoria (in effetti, l'implementazione sembra essere un tick più lento rispetto all'approccio standard)".

D'altro canto, Gallo e altri dicono nel loro libro ASP.NET AJAX in azione (Manning) che "Quando il numero di stringhe da concatenare è maggiore, il generatore di stringhe diventa un oggetto essenziale per evitare enormi cali di prestazioni".

Immagino che dovresti fare il tuo benchmarking e anche i risultati potrebbero differire tra i browser. Tuttavia, anche se non migliora le prestazioni, potrebbe comunque essere considerato "utile" per i programmatori che sono abituati a programmare con StringBuilders in linguaggi come C # o Java.


1

È un post in ritardo, ma non ho trovato una buona citazione del libro tra le risposte.

Ecco un preciso tranne che da un libro affidabile:

Le stringhe sono immutabili in ECMAScript, il che significa che una volta create, i loro valori non possono cambiare. Per modificare la stringa detenuta da una variabile, la stringa originale deve essere distrutta e la variabile deve essere riempita con un'altra stringa contenente un nuovo valore ... — JavaScript professionale per sviluppatori Web, 3a edizione, p.43

Ora, la risposta che cita l'estratto del libro di Rhino ha ragione sull'immutabilità delle stringhe, ma erroneamente dice "Le stringhe sono assegnate per riferimento, non per valore". (probabilmente inizialmente intendevano mettere le parole in modo opposto).

L'idea sbagliata "riferimento / valore" è chiarita nel "JavaScript professionale", capitolo denominato "Valori primitivi e di riferimento":

I cinque tipi primitivi ... [are]: Undefined, Null, Boolean, Number e String. Si dice che queste variabili siano accessibili per valore, perché si sta manipolando il valore effettivo memorizzato nella variabile. — JavaScript professionale per sviluppatori Web, 3a edizione, p.85

che si contrappone agli oggetti :

Quando manipoli un oggetto, stai davvero lavorando a un riferimento a quell'oggetto piuttosto che all'oggetto reale stesso. Per questo motivo, si dice che tali valori sono accessibili per riferimento. — JavaScript professionale per sviluppatori Web, 3a edizione, p.85


FWIW: Il libro di Rhino probabilmente significa che internamente / implementazione un'assegnazione di stringa sta memorizzando / copiando un puntatore (piuttosto che copiare il contenuto della stringa). Non sembra un incidente da parte loro, basato sul testo che segue. Ma sono d'accordo: usano male il termine "per riferimento". Non è "per riferimento" solo perché l'implementazione passa i puntatori (per le prestazioni). Wiki - la strategia di valutazione è una lettura interessante su questo argomento.
ToolmakerSteve


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.