Sind JavaScript-Zeichenfolgen unveränderlich? Benötige ich einen "String Builder" in JavaScript?


237

Verwendet Javascript unveränderliche oder veränderbare Zeichenfolgen? Benötige ich einen "String Builder"?


3
Ja, die y sind unveränderlich und Sie benötigen einen "String Builder". Lesen Sie dieses blog.codeeffects.com/Article/String-Builder-In-Java-Script oder dieses codeproject.com/KB/scripting/stringbuilder.aspx
Kizz

2
Interessanterweise widersprechen diese Beispiele meinen Erkenntnissen in meiner Antwort.
Juan Mendes

Antworten:


294

Sie sind unveränderlich. Sie können ein Zeichen innerhalb einer Zeichenfolge nicht mit so etwas wie ändern var myString = "abbdef"; myString[2] = 'c'. Die String - Manipulationsmethoden wie trim, slicezurückkehren neue Saiten.

Wenn Sie zwei Verweise auf dieselbe Zeichenfolge haben, wirkt sich das Ändern einer auf die andere nicht aus

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

Ich habe jedoch immer gehört, was Ash in seiner Antwort erwähnt hat (dass die Verwendung von Array.join für die Verkettung schneller ist), daher wollte ich die verschiedenen Methoden zum Verketten von Zeichenfolgen und zum Abstrahieren des schnellsten Wegs in einen StringBuilder testen. Ich habe einige Tests geschrieben, um zu sehen, ob dies wahr ist (ist es nicht!).

Dies war meiner Meinung nach der schnellste Weg, obwohl ich immer wieder dachte, dass das Hinzufügen eines Methodenaufrufs ihn langsamer machen könnte ...

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

Hier sind Leistungsgeschwindigkeitstests. Alle drei bilden eine gigantische Zeichenfolge, die aus der "Hello diggity dog"hunderttausendfachen Verkettung zu einer leeren Zeichenfolge besteht.

Ich habe drei Arten von Tests erstellt

  • Verwenden von Array.pushundArray.join
  • Verwenden der Array-Indizierung, um dies zu vermeiden Array.push, und Verwenden vonArray.join
  • Verkettung von geraden Zeichenfolgen

Dann habe ich die gleichen drei Tests , die von ihnen in abstrahiert StringBuilderConcat, StringBuilderArrayPushund StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Bitte gehen Sie dort und laufen Tests , so dass wir eine schöne Probe erhalten. Beachten Sie, dass ich einen kleinen Fehler behoben habe, sodass die Daten für die Tests gelöscht wurden. Ich werde die Tabelle aktualisieren, sobald genügend Leistungsdaten vorhanden sind. Gehen Sie zu http://jsperf.com/string-concat-without-sringbuilder/5Die alte Datentabelle finden .

Hier sind einige Zahlen (letztes Update in Ma5rch 2018), wenn Sie dem Link nicht folgen möchten. Die Zahl bei jedem Test ist in 1000 Operationen / Sekunde ( höher ist besser )

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

Ergebnisse

  • Heutzutage handhaben alle immergrünen Browser die Verkettung von Zeichenfolgen gut. Array.joinhilft nur IE 11

  • Insgesamt ist Opera am schnellsten, viermal so schnell wie Array.join

  • Firefox ist an zweiter Stelle und Array.joinin FF nur geringfügig langsamer, in Chrome jedoch erheblich langsamer (3x).

  • Chrome ist an dritter Stelle, aber String Concat ist dreimal schneller als Array.join

  • Das Erstellen eines StringBuilder scheint die Leistung nicht zu stark zu beeinträchtigen.

Hoffe, jemand anderes findet das nützlich

Unterschiedlicher Testfall

Da @RoyTinker meinte, mein Test sei fehlerhaft, habe ich einen neuen Fall erstellt, bei dem keine große Zeichenfolge durch Verketten derselben Zeichenfolge erstellt wird. Für jede Iteration wird ein anderes Zeichen verwendet. Die Verkettung von Zeichenfolgen schien immer noch schneller oder genauso schnell zu sein. Lassen Sie uns diese Tests zum Laufen bringen.

Ich schlage vor, dass jeder über andere Möglichkeiten nachdenken sollte, um dies zu testen, und neue Links zu verschiedenen Testfällen hinzufügen kann.

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


@Juan, der Link, den Sie uns gebeten haben, zu besuchen, verkettet 30 Mal eine Zeichenfolge mit 112 Zeichen. Hier ist ein weiterer Test, der helfen könnte, das Gleichgewicht zu halten: Array.join vs. String-Verkettung für 20.000 verschiedene 1- Zeichen -Strings (Join ist in IE / FF viel schneller). jsperf.com/join-vs-str-concat-large-array
Roy Tinker

1
@ RoyTinker Roy, oh Roy, deine Tests betrügen, weil du das Array im Setup des Tests erstellst. Hier ist der eigentliche Test mit verschiedenen Zeichen jsperf.com/string-concat-without-sringbuilder/7 Sie können gerne neue Testfälle erstellen, aber das Erstellen des Arrays ist Teil des Tests selbst
Juan Mendes

@JuanMendes Mein Ziel war es, den Testfall auf einen strengen Vergleich zwischen joinVerkettung und String-Verkettung zu beschränken und so das Array vor dem Test zu erstellen . Ich denke nicht, dass das Betrug ist, wenn dieses Ziel verstanden wird (und joindas Array intern auflistet, so dass es auch kein Betrug ist, eine forSchleife aus dem joinTest wegzulassen ).
Roy Tinker

2
@ RoyTinker Ja, jeder String Builder muss das Array erstellen. Die Frage ist, ob ein String Builder benötigt wird. Wenn Sie die Zeichenfolgen bereits in einem Array haben, ist dies kein gültiger Testfall für das, was wir hier diskutieren
Juan Mendes

1
@ Baltusaj Die Spalten, die Index / Push sagen, verwendenArray.join
Juan Mendes

41

aus dem Nashornbuch :

In JavaScript sind Zeichenfolgen unveränderliche Objekte. Dies bedeutet, dass die darin enthaltenen Zeichen möglicherweise nicht geändert werden und dass Operationen an Zeichenfolgen tatsächlich neue Zeichenfolgen erstellen. Zeichenfolgen werden nach Referenz und nicht nach Wert zugewiesen. Wenn ein Objekt durch Referenz zugewiesen wird, ist im Allgemeinen eine Änderung, die durch eine Referenz am Objekt vorgenommen wurde, durch alle anderen Referenzen auf das Objekt sichtbar. Da Zeichenfolgen jedoch nicht geändert werden können, können Sie mehrere Verweise auf ein Zeichenfolgenobjekt haben und müssen sich keine Sorgen machen, dass sich der Zeichenfolgenwert ändert, ohne dass Sie es wissen


7
Link zu einem entsprechenden Abschnitt des
Nashornbuchs

116
Das Rhino-Buchzitat (und damit diese Antwort) ist hier falsch . In JavaScript sind Zeichenfolgen primitive Werttypen und keine Objekte (spec) . Ab ES5 sind sie neben null undefined numberund einer der wenigen 5 Werttypen boolean. Zeichenfolgen werden nach Wert und nicht nach Referenz zugewiesen und als solche übergeben. Strings sind also nicht nur unveränderlich, sie sind ein Wert . Die Zeichenfolge ändern "hello"zu sein "world"ist wie die von nun an die Nummer 3 der Entscheidung ist die Nummer 4 ... es macht keinen Sinn.
Benjamin Gruenbaum

8
Ja, wie mein Kommentar sagt Strings sind unveränderlich, aber sie sind nicht Referenztypen noch sind sie Objekte - sie sind primitive Werttypen. Ein einfacher Weg, um zu sehen, dass sie keine sind, wäre zu versuchen, einer Zeichenfolge eine Eigenschaft hinzuzufügen und sie dann zu lesen:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum

9
@ VidarS.Ramdal Nein, Stringmit dem Zeichenfolgenkonstruktor erstellte Objekte sind Wrapper um JavaScript-Zeichenfolgenwerte. Mit der .valueOf()Funktion können Sie auf den Zeichenfolgenwert des Box-Typs zugreifen. Dies gilt auch für NumberObjekte und Zahlenwerte. Es ist wichtig zu beachten, dass StringObjekte, die mit erstellt wurden, new Stringkeine tatsächlichen Zeichenfolgen sind, sondern Wrapper oder Kästchen um Zeichenfolgen. Siehe es5.github.io/#x15.5.2.1 . Informationen
Benjamin Gruenbaum,

5
Warum manche Leute sagen, dass Strings Objekte sind, stammen wahrscheinlich aus Python oder Lisp oder einer anderen Sprache, in der das Wort "Objekt" für jede Art von Datum (sogar Ganzzahlen) verwendet wird. Sie müssen nur lesen, wie die ECMA-Spezifikation das Wort "Mitglied des Typs Objekt" definiert. Auch das Wort "Wert" kann je nach Spezifikation verschiedener Sprachen unterschiedliche Bedeutungen haben.
Jisang Yoo

21

Leistungstipp:

Wenn Sie große Zeichenfolgen verketten müssen, fügen Sie die Zeichenfolgenteile in ein Array ein und verwenden Sie die Array.Join()Methode, um die Gesamtzeichenfolge abzurufen. Dies kann für die Verkettung einer großen Anzahl von Zeichenfolgen um ein Vielfaches schneller sein.

StringBuilderIn JavaScript gibt es keine .


Ich weiß, dass es keinen stringBuilder gibt, msAjax hat einen, und ich habe nur darüber nachgedacht, ob er nützlich ist oder nicht
DevelopingChris

5
Was hat das damit zu tun, dass Strings unveränderlich sind oder nicht?
Baudtack

4
@docgnome: Da Zeichenfolgen unveränderlich sind, erfordert die Verkettung von Zeichenfolgen das Erstellen von mehr Objekten als der Array.join-Ansatz
Juan Mendes

8
Laut Juans obigem Test ist die Verkettung von Zeichenfolgen sowohl im IE als auch in Chrome schneller, während sie in Firefox langsamer ist.
Bill Yang

9
Erwägen Sie, Ihre Antwort zu aktualisieren. Möglicherweise war dies vor langer Zeit der Fall, aber es ist nicht mehr so. Siehe jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes

8

Nur um für einfache Köpfe wie meine (von MDN ) zu klären :

Unveränderliche Objekte sind Objekte, deren Status nach dem Erstellen des Objekts nicht mehr geändert werden kann.

Zeichenfolge und Zahlen sind unveränderlich.

Unveränderlich bedeutet:

Sie können einen Variablennamen auf einen neuen Wert verweisen lassen, der vorherige Wert wird jedoch weiterhin gespeichert. Daher die Notwendigkeit der Müllabfuhr.

var immutableString = "Hello";

// Im obigen Code wird ein neues Objekt mit einem Zeichenfolgenwert erstellt.

immutableString = immutableString + "World";

// Wir hängen jetzt "World" an den vorhandenen Wert an.

Es sieht so aus, als würden wir den String 'immutableString' mutieren, aber das sind wir nicht. Stattdessen:

Beim Anhängen des "immutableString" an einen Zeichenfolgenwert treten folgende Ereignisse auf:

  1. Der vorhandene Wert von "immutableString" wird abgerufen
  2. "World" wird an den vorhandenen Wert von "immutableString" angehängt.
  3. Der resultierende Wert wird dann einem neuen Speicherblock zugewiesen
  4. Das Objekt "immutableString" zeigt jetzt auf den neu erstellten Speicherplatz
  5. Zuvor erstellter Speicherplatz ist jetzt für die Speicherbereinigung verfügbar.

4

Der Wert für den Zeichenfolgentyp ist unveränderlich, aber das Zeichenfolgenobjekt , das mit dem Konstruktor String () erstellt wird, kann geändert werden, da es sich um ein Objekt handelt und Sie ihm neue Eigenschaften hinzufügen können.

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

Obwohl Sie neue Eigenschaften hinzufügen können, können Sie die bereits vorhandenen Eigenschaften nicht ändern

Ein Screenshot eines Tests in der Chrome-Konsole

Zusammenfassend ist 1. der gesamte Wert des Zeichenfolgentyps (primitiver Typ) unveränderlich. 2. Das String-Objekt ist veränderbar, aber der darin enthaltene String-Typ-Wert (primitiver Typ) ist unveränderlich.


Javascript String-Objekte sind unveränderlich developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun

@prasun, aber auf dieser Seite heißt es: "Alle Typen außer Objekten definieren unveränderliche Werte (Werte, die nicht geändert werden können)." String-Objekte sind Objekte. Und wie ist es unveränderlich, wenn Sie neue Eigenschaften hinzufügen können?
Zhanziyang

Lesen Sie den Abschnitt "String Type". Der String-Link des Javascript verweist sowohl auf das Grundelement als auch auf das Objekt. Später heißt es "JavaScript-Strings sind unveränderlich". Es sieht so aus, als ob das Dokument zu diesem Thema nicht klar ist, da es bei zwei verschiedenen Notizen in Konflikt steht
prasun

6
new Stringerzeugt einen veränderlichen Wrapper um einen unveränderlichen String
tomdemuyt

1
Es ist sehr einfach zu testen, indem Sie den obigen Code von @ zhanziyang ausführen. Sie können einem StringObjekt (Wrapper) vollständig neue Eigenschaften hinzufügen , was bedeutet, dass es nicht unveränderlich ist (standardmäßig; wie jedes andere Objekt, das Sie aufrufen Object.freezekönnen, um es unveränderlich zu machen). Ein primitiver Zeichenfolgenwerttyp, unabhängig davon, ob er in einem StringObjektwrapper enthalten ist oder nicht, ist immer unveränderlich.
Mark Reed

3

Saiten sind unveränderlich - sie können sich nicht ändern, wir können immer nur neue Saiten erstellen.

Beispiel:

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

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

1

In Bezug auf Ihre Frage (in Ihrem Kommentar zu Ashs Antwort) zum StringBuilder in ASP.NET Ajax scheinen sich die Experten in dieser Frage nicht einig zu sein.

Christian Wenz sagt in seinem Buch Programming ASP.NET AJAX (O'Reilly), dass "dieser Ansatz keine messbaren Auswirkungen auf das Gedächtnis hat (tatsächlich scheint die Implementierung ein Tick langsamer zu sein als der Standardansatz)."

Auf der anderen Seite sagen Gallo et al. In ihrem Buch ASP.NET AJAX in Action (Manning): "Wenn die Anzahl der zu verkettenden Zeichenfolgen größer ist, wird der Zeichenfolgengenerator zu einem wesentlichen Objekt, um enorme Leistungseinbußen zu vermeiden."

Ich denke, Sie müssten Ihr eigenes Benchmarking durchführen, und die Ergebnisse können auch zwischen den Browsern unterschiedlich sein. Selbst wenn es die Leistung nicht verbessert, kann es dennoch als "nützlich" für Programmierer angesehen werden, die es gewohnt sind, mit StringBuilders in Sprachen wie C # oder Java zu codieren.


1

Es ist ein später Beitrag, aber ich habe unter den Antworten kein gutes Buchzitat gefunden.

Hier ist eine eindeutige Ausnahme von einem zuverlässigen Buch:

Zeichenfolgen sind in ECMAScript unveränderlich, was bedeutet, dass sich ihre Werte nach ihrer Erstellung nicht ändern können. Um die von einer Variablen gehaltene Zeichenfolge zu ändern, muss die ursprüngliche Zeichenfolge zerstört und die Variable mit einer anderen Zeichenfolge gefüllt werden, die einen neuen Wert enthält ... - Professionelles JavaScript für Webentwickler, 3. Ausgabe, S. 43

Die Antwort, die den Auszug aus dem Rhino-Buch zitiert, ist richtig in Bezug auf die Unveränderlichkeit von Zeichenfolgen, aber falsch: "Zeichenfolgen werden durch Referenz und nicht durch Wert zugewiesen." (wahrscheinlich wollten sie die Wörter ursprünglich anders ausdrücken).

Das Missverständnis "Referenz / Wert" wird im Kapitel "Professionelles JavaScript" im Kapitel "Primitive und Referenzwerte" erläutert:

Die fünf primitiven Typen ... [sind]: Undefiniert, Null, Boolesch, Zahl und Zeichenfolge. Auf diese Variablen wird als Wert zugegriffen, da Sie den in der Variablen gespeicherten tatsächlichen Wert bearbeiten. - Professionelles JavaScript für Webentwickler, 3. Aufl., S. 85

das ist gegen Objekte :

Wenn Sie ein Objekt bearbeiten, arbeiten Sie wirklich an einem Verweis auf dieses Objekt und nicht auf das eigentliche Objekt. Aus diesem Grund soll auf solche Werte als Referenz zugegriffen werden. - Professionelles JavaScript für Webentwickler, 3. Aufl., S. 85


FWIW: Das Rhino-Buch bedeutet wahrscheinlich, dass intern / Implementierung einer Zeichenfolgenzuweisung ein Zeiger gespeichert / kopiert wird (anstatt den Inhalt der Zeichenfolge zu kopieren). Es sieht nicht nach einem Unfall aus, basierend auf dem Text danach. Aber ich stimme zu: Sie missbrauchen den Begriff "durch Bezugnahme". Es ist nicht "als Referenz", nur weil die Implementierung Zeiger (für die Leistung) übergibt. Wiki - Bewertungsstrategie ist eine interessante Lektüre zu diesem Thema.
ToolmakerSteve


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.