Was ist der Unterschied zwischen String-Grundelementen und String-Objekten in JavaScript?


116

Entnommen aus MDN

Zeichenfolgenliterale (gekennzeichnet durch doppelte oder einfache Anführungszeichen) und Zeichenfolgen, die von Zeichenfolgenaufrufen in einem Nichtkonstruktorkontext (dh ohne Verwendung des neuen Schlüsselworts) zurückgegeben werden, sind primitive Zeichenfolgen. JavaScript konvertiert Primitive automatisch in String-Objekte, sodass String-Objektmethoden für primitive Strings verwendet werden können. In Kontexten, in denen eine Methode für eine primitive Zeichenfolge aufgerufen werden soll oder eine Eigenschaftssuche erfolgt, wird das Zeichenfolgenprimitiv von JavaScript automatisch umbrochen und die Methode aufgerufen oder die Eigenschaftssuche durchgeführt.

Daher dachte ich, dass (logisch) Operationen (Methodenaufrufe) für Zeichenfolgenprimitive langsamer sein sollten als Operationen für Zeichenfolgenobjekte, da jedes Zeichenfolgenprimitiv in Zeichenfolgenobjekt konvertiert wird (zusätzliche Arbeit), bevor methodes auf die Zeichenfolge angewendet wird.

In diesem Testfall ist das Ergebnis jedoch umgekehrt. Der Codeblock 1 läuft schneller als der Codeblock 2 , beide Codeblöcke sind unten angegeben:

Codeblock-1:

var s = '0123456789';
for (var i = 0; i < s.length; i++) {
  s.charAt(i);
}

Codeblock 2:

var s = new String('0123456789');
for (var i = 0; i < s.length; i++) {
    s.charAt(i);
}

Die Ergebnisse variieren in Browsern, aber der Codeblock 1 ist immer schneller. Kann jemand bitte erklären, warum der Codeblock-1 schneller ist als der Codeblock-2 .


6
Verwendung new Stringführt eine weitere transparente Schicht aus Objektverpackung. typeof new String(); //"object"
Paul S.

was ist mit '0123456789'.charAt(i)?
Yuriy Galanter

@YuriyGalanter, es ist kein Problem, aber ich frage, warum code block-1ist schneller?
Die Alpha

2
String-Objekte sind im realen Kontext relativ selten zu sehen, daher ist es für Interpreten nicht überraschend, String-Literale zu optimieren. Heutzutage wird Ihr Code nicht einfach interpretiert , es gibt viele Optimierungsebenen, die hinter den Kulissen stattfinden.
Fabrício Matté

2
Das ist komisch: Revision 2
hjpotter92

Antworten:


149

JavaScript hat zwei Haupttypkategorien, Grundelemente und Objekte.

var s = 'test';
var ss = new String('test');

Die Muster für einfache Anführungszeichen / doppelte Anführungszeichen sind hinsichtlich der Funktionalität identisch. Abgesehen davon wird das Verhalten, das Sie benennen möchten, als Auto-Boxing bezeichnet. Was also tatsächlich passiert, ist, dass ein Grundelement in seinen Wrapper-Typ konvertiert wird, wenn eine Methode des Wrapper-Typs aufgerufen wird. Einfach ausgedrückt:

var s = 'test';

Ist ein primitiver Datentyp. Es hat keine Methoden, es ist nichts weiter als ein Zeiger auf eine Rohdatenspeicherreferenz, was die viel schnellere Direktzugriffsgeschwindigkeit erklärt.

Was passiert also s.charAt(i)zum Beispiel?

Da sist keine Instanz String, Auto-Box JavaScript Willen s, das hat typeof stringseinen Wrapper - Typen, Stringmit typeof objectgenauer s.valueOf(s).prototype.toString.call = [object String].

Das Auto-Boxing-Verhalten wechselt snach Bedarf hin und her zu seinem Wrapper-Typ, aber die Standardoperationen sind unglaublich schnell, da es sich um einen einfacheren Datentyp handelt. Allerdings Auto-Boxen und Object.prototype.valueOfhaben unterschiedliche Effekte.

Wenn Sie das automatische Boxen erzwingen oder ein Grundelement in seinen Wrapper-Typ umwandeln möchten, können Sie es verwenden Object.prototype.valueOf, aber das Verhalten ist anders. Basierend auf einer Vielzahl von Testszenarien wendet Auto-Boxing nur die "erforderlichen" Methoden an, ohne die primitive Natur der Variablen zu ändern. Deshalb bekommst du eine bessere Geschwindigkeit.


33

Dies ist ziemlich implementierungsabhängig, aber ich werde es versuchen. Ich werde mit V8 veranschaulichen, aber ich gehe davon aus, dass andere Motoren ähnliche Ansätze verwenden.

Ein String-Grundelement wird zu einem v8::StringObjekt analysiert . Daher können Methoden direkt darauf aufgerufen werden, wie von jfriend00 erwähnt .

Ein String-Objekt hingegen wird zu einem Objekt analysiert, v8::StringObjectdas sich erstreckt Objectund nicht nur ein vollwertiges Objekt ist, sondern auch als Wrapper für dient v8::String.

Jetzt ist es nur logisch, ein Aufruf von new String('').method()muss dies entpacken v8::StringObject, v8::Stringbevor die Methode ausgeführt wird, daher ist es langsamer.


In vielen anderen Sprachen haben primitive Werte keine Methoden.

Die Art und Weise, wie MDN es ausdrückt, scheint die einfachste Möglichkeit zu sein, zu erklären, wie das Auto-Boxing von Primitiven funktioniert (wie auch in Flavs Antwort erwähnt), dh wie die primitiven y- Werte von JavaScript Methoden aufrufen können.

Eine Smart Engine konvertiert jedoch nicht jedes Mal, wenn Sie eine Methode aufrufen müssen , ein String- Primitiv-y in ein String-Objekt. Dies wird auch informativ in der Annotated ES5-Spezifikation erwähnt. in Bezug auf die Auflösung von Eigenschaften (und "Methoden" ¹) primitiver Werte:

HINWEIS Auf das Objekt, das in Schritt 1 erstellt werden kann, kann außerhalb der obigen Methode nicht zugegriffen werden. Eine Implementierung kann die tatsächliche Erstellung des Objekts vermeiden. [...]

Auf sehr niedrigem Niveau werden Strings am häufigsten als unveränderliche Skalarwerte implementiert. Beispiel für eine Wrapper-Struktur:

StringObject > String (> ...) > char[]

Je weiter Sie vom Primitiven entfernt sind, desto länger wird es dauern, bis Sie es erreichen. In der Praxis sind StringGrundelemente viel häufiger als StringObjects, daher ist es für Engines keine Überraschung, der Klasse der entsprechenden (interpretierten) Objekte der String-Grundelemente Methoden hinzuzufügen, anstatt zwischen Stringund hin und her zu konvertieren, StringObjectwie es die Erklärung von MDN nahe legt.


¹ In JavaScript ist "Methode" nur eine Namenskonvention für eine Eigenschaft, die in einen Wert vom Typ Funktion aufgelöst wird.


1
Bitte. =]Jetzt frage ich mich, ob die Erklärung von MDN vorhanden ist, nur weil es der einfachste Weg zu sein scheint, Auto-Boxing zu verstehen, oder ob es in der ES-Spezifikation einen Hinweis darauf gibt Aktualisieren Sie die Antwort, wenn ich jemals eine Referenz finde.
Fabrício Matté

Großartiger Einblick in die Implementierung des V8. Ich werde hinzufügen, dass Boxen nicht nur dazu da ist, die Funktion zu lösen. Es ist auch da, um diese Referenz in die Methode zu übergeben. Jetzt bin ich mir nicht sicher, ob V8 dies für integrierte Methoden überspringt, aber wenn Sie Ihre eigene Erweiterung hinzufügen, um String.prototype zu sagen, erhalten Sie bei jedem Aufruf eine Box-Version des String-Objekts.
Ben

17

Im Falle eines String-Literal können wir keine Eigenschaften zuweisen

var x = "hello" ;
x.y = "world";
console.log(x.y); // this will print undefined

Im Fall von String Object können wir Eigenschaften zuweisen

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world

1
Schließlich motiviert jemand, warum wir auch StringObjekte brauchen . Danke dir!
Ciprian Tomoiagă

1
Warum sollte jemand das tun müssen?
Aditya

11

String Literal:

String-Literale sind unveränderlich, was bedeutet, dass ihr Status nach ihrer Erstellung nicht mehr geändert werden kann, wodurch sie auch threadsicher sind.

var a = 's';
var b = 's';

a==b Das Ergebnis ist 'true'. Beide Zeichenfolgen verweisen auf dasselbe Objekt.

String-Objekt:

Hier werden zwei verschiedene Objekte erstellt, die unterschiedliche Referenzen haben:

var a = new String("s");
var b = new String("s");

a==b Das Ergebnis ist falsch, da sie unterschiedliche Referenzen haben.


1
Ist das String-Objekt auch unveränderlich?
Yang Wang

@ YangWang, das ist eine dumme Sprache, für beide a& bversuchen, sie zuzuweisen a[0] = 'X', wird erfolgreich ausgeführt, funktioniert aber nicht wie erwartet
ruX

Sie haben geschrieben: "var a = 's'; var b = 's'; a == b Ergebnis ist 'wahr', beide Zeichenfolgen verweisen auf dasselbe Objekt." Das ist nicht richtig: a und b beziehen sich nicht auf dasselbe Objekt, das Ergebnis ist wahr, weil sie denselben Wert haben. Diese Werte werden an verschiedenen Speicherorten gespeichert. Wenn Sie sie ändern, ändert sich der andere nicht!
SC1000

9

Wenn Sie verwenden new, geben Sie ausdrücklich an, dass Sie eine Instanz eines Objekts erstellen möchten . Daher new Stringwird ein Objekt erstellt , das das String-Grundelement umschließt, was bedeutet, dass jede Aktion darauf eine zusätzliche Arbeitsebene erfordert.

typeof new String(); // "object"
typeof '';           // "string"

Da es sich um verschiedene Typen handelt, kann Ihr JavaScript- Interpreter sie auch unterschiedlich optimieren, wie in den Kommentaren erwähnt .


5

Wenn Sie erklären:

var s = '0123456789';

Sie erstellen ein Zeichenfolgenprimitiv. Dieses Zeichenfolgenprimitiv verfügt über Methoden, mit denen Sie Methoden aufrufen können, ohne das Grundelement in ein erstklassiges Objekt zu konvertieren. Ihre Annahme, dass dies langsamer wäre, weil die Zeichenfolge in ein Objekt konvertiert werden muss, ist also nicht korrekt. Es muss nicht in ein Objekt konvertiert werden. Das Grundelement selbst kann die Methoden aufrufen.

Das Konvertieren in ein vollständiges Objekt (mit dem Sie neue Eigenschaften hinzufügen können) ist ein zusätzlicher Schritt und beschleunigt die Zeichenfolgenoperationen nicht schneller (tatsächlich zeigt Ihr Test, dass sie dadurch langsamer werden).


Wie kommt es, dass das String-Grundelement alle Prototyp-Eigenschaften einschließlich benutzerdefinierter Eigenschaften erbt String.prototype?
Yuriy Galanter

1
var s = '0123456789';ist ein primitiver Wert, wie kann dieser Wert Methoden haben, ich bin verwirrt!
Die Alpha

2
@SheikhHeera - Grundelemente sind in die Sprachimplementierung integriert, damit der Interpreter ihnen besondere Befugnisse erteilen kann.
jfriend00

1
@ SheikhHeera - Ich verstehe deinen letzten Kommentar / deine letzte Frage nicht. Ein String-Grundelement an sich unterstützt Sie nicht dabei, eigene Eigenschaften hinzuzufügen. Um dies zu ermöglichen, verfügt Javascript auch über ein String-Objekt, das dieselben Methoden wie ein String-Grundelement hat, aber ein vollwertiges Objekt ist, das Sie auf alle Arten wie ein Objekt behandeln können. Diese doppelte Form scheint etwas chaotisch zu sein, aber ich vermute, dass sie als Leistungskompromiss durchgeführt wurde, da im 99% -Fall Primitive verwendet werden und sie wahrscheinlich schneller und speichereffizienter als Zeichenfolgenobjekte sein können.
jfriend00

1
@SheikhHeera "in Zeichenfolgenobjekt konvertiert" drückt MDN dies aus, um zu erklären, wie Grundelemente Methoden aufrufen können. Sie werden nicht buchstäblich in Zeichenfolgenobjekte konvertiert.
Fabrício Matté

4

Ich kann sehen, dass diese Frage vor langer Zeit gelöst wurde. Es gibt eine weitere subtile Unterscheidung zwischen String-Literalen und String-Objekten, da niemand sie berührt zu haben scheint. Ich dachte, ich würde sie der Vollständigkeit halber nur schreiben.

Grundsätzlich besteht eine weitere Unterscheidung zwischen beiden bei der Verwendung von eval. eval ('1 + 1') ergibt 2, während eval (neuer String ('1 + 1')) '1 + 1' ergibt. Wenn also ein bestimmter Codeblock sowohl 'normal' als auch mit eval ausgeführt werden kann, könnte dies der Fall sein führen zu seltsamen Ergebnissen


Vielen Dank für Ihre Eingabe :-)
The Alpha

Wow, das ist ein wirklich komisches Verhalten. Sie sollten Ihrem Kommentar eine kleine Inline-Demo hinzufügen, um dieses Verhalten zu zeigen - es öffnet extrem die Augen.
EyuelDK

Das ist normal, wenn Sie darüber nachdenken. new String("")Geben Sie ein Objekt zurück und bewerten Sie nur die Zeichenfolge und geben Sie alles andere so zurück, wie es ist
Félix Brunet

3

Die Existenz eines Objekts hat wenig mit dem tatsächlichen Verhalten eines Strings in ECMAScript / JavaScript-Engines zu tun, da der Stammbereich lediglich Funktionsobjekte dafür enthält. Daher wird die Funktion charAt (int) im Fall eines Zeichenfolgenliteral gesucht und ausgeführt.

Bei einem realen Objekt fügen Sie eine weitere Ebene hinzu, in der die charAt (int) -Methode auch für das Objekt selbst durchsucht wird, bevor das Standardverhalten einsetzt (wie oben). Anscheinend wird in diesem Fall überraschend viel Arbeit geleistet.

Übrigens glaube ich nicht, dass Grundelemente tatsächlich in Objekte konvertiert werden, aber die Skript-Engine markiert diese Variable einfach als Zeichenfolgentyp und kann daher alle bereitgestellten Funktionen dafür finden, sodass es so aussieht, als würden Sie ein Objekt aufrufen. Vergessen Sie nicht, dass dies eine Skriptlaufzeit ist, die nach anderen Prinzipien als eine OO-Laufzeit arbeitet.


3

Der größte Unterschied zwischen einem Zeichenfolgenprimitiv und einem Zeichenfolgenobjekt besteht darin, dass Objekte für den ==Operator dieser Regel folgen müssen :

Ein Ausdruck, der Objekte vergleicht, ist nur wahr, wenn die Operanden auf dasselbe Objekt verweisen.

Während String-Grundelemente einen praktischen ==Wert haben, der den Wert vergleicht, haben Sie kein Glück, wenn es darum geht, dass sich ein anderer unveränderlicher Objekttyp (einschließlich eines String-Objekts) wie ein Werttyp verhält.

"hello" == "hello"
-> true
new String("hello") == new String("hello") // beware!
-> false

(Andere haben festgestellt, dass ein Zeichenfolgenobjekt technisch veränderbar ist, da Sie ihm Eigenschaften hinzufügen können. Es ist jedoch nicht klar, wofür dies nützlich ist. Der Zeichenfolgenwert selbst ist nicht veränderbar.)


Vielen Dank, dass Sie der Frage nach einiger Zeit einen Mehrwert verliehen haben :-)
The Alpha

1

Der Code wird optimiert, bevor er von der Javascript-Engine ausgeführt wird. Im Allgemeinen können Mikro-Benchmarks irreführend sein, da Compiler und Interpreter Teile Ihres Codes neu anordnen, ändern, entfernen und andere Tricks ausführen, damit er schneller ausgeführt wird. Mit anderen Worten, der geschriebene Code gibt an, was das Ziel ist, aber der Compiler und / oder die Laufzeit entscheiden, wie dieses Ziel erreicht werden soll.

Block 1 ist schneller, hauptsächlich wegen: var s = '0123456789'; ist immer schneller als var s = new String ('0123456789'); wegen des Overheads der Objekterstellung.

Der Schleifenteil ist nicht derjenige, der die Verlangsamung verursacht, da chartAt () vom Interpreter eingefügt werden kann. Versuchen Sie, die Schleife zu entfernen, und führen Sie den Test erneut aus. Sie werden feststellen, dass das Geschwindigkeitsverhältnis dem entspricht, als ob die Schleife nicht entfernt worden wäre. Mit anderen Worten, für diese Tests haben die Schleifenblöcke zur Ausführungszeit genau den gleichen Bytecode / Maschinencode.

Bei diesen Arten von Mikro-Benchmarks liefert ein Blick auf den Bytecode oder den Maschinencode ein klareres Bild.


1
Danke für deine Antwort.
Die Alpha

0

In Javascript sind primitive Datentypen wie string ein nicht zusammengesetzter Baustein. Dies bedeutet, dass es sich nur um Werte handelt, nicht mehr: let a = "string value"; Standardmäßig gibt es keine integrierten Methoden wie toUpperCase, toLowerCase usw.

Aber wenn Sie versuchen zu schreiben:

console.log( a.toUpperCase() ); or console.log( a.toLowerCase() );

Dies wird keinen Fehler auslösen, stattdessen funktionieren sie wie sie sollten.

Was ist passiert ? Wenn Sie versuchen, auf eine Eigenschaft einer Zeichenfolge azuzugreifen, zwingt Javascript die Zeichenfolge zu einem Objekt, new String(a);das als Wrapper-Objekt bezeichnet wird .

Dieser Prozess ist mit dem Konzept der Funktionskonstruktoren in Javascript verknüpft , bei dem Funktionen zum Erstellen neuer Objekte verwendet werden.

Wenn Sie geben new String('String value');hier String Funktion Konstruktor, der ein Argument nimmt und erzeugt ein leeres Objekt innerhalb des Funktionsumfang, das leere Objekt zugewiesen ist dies , und in diesem Fall liefert String alle diejenigen , die in Funktionen gebaut bekannt wir bereits erwähnt. Sobald der Vorgang abgeschlossen ist, z. B. in Großbuchstaben, wird das Wrapper-Objekt verworfen.

Um dies zu beweisen, machen wir Folgendes:

let justString = 'Hello From String Value';
justString.addNewProperty = 'Added New Property';
console.log( justString );

Hier wird die Ausgabe undefiniert. Warum ? In diesem Fall erstellt Javascript ein Wrapper-String-Objekt, legt die neue Eigenschaft addNewProperty fest und verwirft das Wrapper-Objekt sofort. Deshalb wirst du undefiniert. Pseudocode würde folgendermaßen aussehen:

let justString = 'Hello From String Value';
let wrapperObject = new String( justString );
wrapperObject.addNewProperty = 'Added New Property'; //Do operation and discard

0

Wir können String auf drei Arten definieren

  1. var a = "erster Weg";
  2. var b = String ("zweiter Weg");
  3. var c = neuer String ("dritter Weg");

// wir können auch mit 4 erstellen. var d = a + '';

Überprüfen Sie den Typ der mit dem Operator typeof erstellten Zeichenfolgen

  • Typ eines // "Strings"
  • typeof b // "string"
  • Typ von c // "Objekt"


wenn Sie a und b var vergleichen a==b ( // yes)


wenn Sie ein String-Objekt vergleichen

var StringObj = new String("third way")
var StringObj2 = new String("third way")
StringObj  == StringObj2 // no result will be false, because they have different references
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.