Qual è la differenza tra le primitive di stringa e gli oggetti String in JavaScript?


116

Tratto da MDN

I letterali stringa (indicati da virgolette doppie o singole) e le stringhe restituite da chiamate String in un contesto non di costruzione (cioè, senza utilizzare la nuova parola chiave) sono stringhe primitive. JavaScript converte automaticamente le primitive in oggetti String, in modo che sia possibile utilizzare i metodi degli oggetti String per le stringhe primitive. Nei contesti in cui un metodo deve essere invocato su una stringa primitiva o si verifica una ricerca di proprietà, JavaScript avvolgerà automaticamente la primitiva di stringa e chiamerà il metodo o eseguirà la ricerca di proprietà.

Quindi, ho pensato (logicamente) le operazioni (chiamate di metodo) sulle primitive di stringa dovrebbero essere più lente delle operazioni su oggetti stringa perché qualsiasi primitiva stringa viene convertita in oggetto stringa (lavoro extra) prima di methodessere applicata sulla stringa.

Ma in questo caso di test , il risultato è opposto. Il blocco di codice 1 viene eseguito più velocemente del blocco di codice 2 , entrambi i blocchi di codice sono riportati di seguito:

blocco di codice-1:

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

blocco di codice 2:

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

I risultati variano nei browser, ma il blocco di codice 1 è sempre più veloce. Qualcuno può spiegare questo, perché il blocco di codice 1 è più veloce del blocco di codice 2 .


6
L'utilizzo new Stringintroduce un altro strato trasparente di avvolgimento dell'oggetto . typeof new String(); //"object"
Paul S.

di cosa '0123456789'.charAt(i)?
Yuriy Galanter

@YuriyGalanter, non è un problema ma mi chiedo perché code block-1è più veloce?
L'alfa

2
Gli oggetti stringa sono relativamente rari da vedere nel contesto della vita reale, quindi non sorprende che gli interpreti ottimizzino i letterali stringa. Al giorno d'oggi, il tuo codice non viene semplicemente interpretato , ci sono molti livelli di ottimizzazione che avvengono dietro le quinte.
Fabrício Matté

2
Questo è strano: revisione 2
hjpotter92

Risposte:


149

JavaScript ha due categorie di tipi principali, primitive e oggetti.

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

I modelli virgolette singole / virgolette doppie sono identici in termini di funzionalità. A parte questo, il comportamento che stai cercando di nominare si chiama auto-boxing. Quindi ciò che effettivamente accade è che una primitiva viene convertita nel suo tipo wrapper quando viene richiamato un metodo del tipo wrapper. In parole semplici:

var s = 'test';

È un tipo di dati primitivo. Non ha metodi, non è altro che un puntatore a un riferimento alla memoria di dati grezzi, il che spiega la velocità di accesso casuale molto più veloce.

Quindi cosa succede quando lo fai s.charAt(i)per esempio?

Poiché snon è un'istanza di String, JavaScript inserirà automaticamente il box s, che ha il typeof stringsuo tipo di wrapper String, con typeof objecto più precisamente s.valueOf(s).prototype.toString.call = [object String].

Il comportamento dell'auto-boxing esegue il cast savanti e indietro al suo tipo di wrapper secondo necessità, ma le operazioni standard sono incredibilmente veloci poiché hai a che fare con un tipo di dati più semplice. Tuttavia auto-boxe e Object.prototype.valueOfhanno effetti diversi.

Se vuoi forzare l'auto-boxing o lanciare una primitiva al suo tipo wrapper, puoi usare Object.prototype.valueOf, ma il comportamento è diverso. Basato su un'ampia varietà di scenari di test, l'auto-boxing applica solo i metodi "richiesti", senza alterare la natura primitiva della variabile. Ecco perché ottieni una velocità migliore.


33

Questo dipende piuttosto dall'implementazione, ma ci proverò. Esemplificherò con V8 ma presumo che altri motori utilizzino approcci simili.

Una primitiva stringa viene analizzata in un v8::Stringoggetto. Quindi, i metodi possono essere invocati direttamente su di esso come menzionato da jfriend00 .

Un oggetto String, d'altra parte, viene analizzato in un v8::StringObjectche si estende Objecte, oltre ad essere un oggetto completo, funge da wrapper per v8::String.

Ora è logico, una chiamata a new String('').method()deve unboxing questo v8::StringObject's v8::Stringprima di eseguire il metodo, quindi è più lento.


In molte altre lingue, i valori primitivi non hanno metodi.

Il modo in cui MDN lo mette sembra essere il modo più semplice per spiegare come funziona l'auto-boxing delle primitive (come menzionato anche nella risposta di flav ), cioè come i valori primitivi-y di JavaScript possono invocare metodi.

Tuttavia, un motore intelligente non convertirà una stringa primitiva-y in un oggetto String ogni volta che è necessario chiamare un metodo. Questo è anche menzionato in modo informativo nelle specifiche ES5 annotate. per quanto riguarda le proprietà risolutive (e i "metodi" ¹) dei valori primitivi:

NOTA L'oggetto che può essere creato nel passaggio 1 non è accessibile al di fuori del metodo precedente. Un'implementazione potrebbe scegliere di evitare l'effettiva creazione dell'oggetto. [...]

A un livello molto basso, le stringhe vengono spesso implementate come valori scalari immutabili. Esempio di struttura wrapper:

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

Più sei lontano dal primitivo, più tempo ci vorrà per arrivarci. In pratica, le Stringprimitive sono molto più frequenti di StringObjects, quindi non è una sorpresa per i motori aggiungere metodi alla classe degli oggetti corrispondenti (interpretati) delle primitive String invece di convertirli avanti e indietro tra Stringe StringObjectcome suggerisce la spiegazione di MDN.


¹ In JavaScript, "metodo" è solo una convenzione di denominazione per una proprietà che si risolve in un valore di tipo funzione.


1
Prego. =]Ora mi chiedo se la spiegazione di MDN sia lì solo perché sembra essere il modo più semplice per capire l'auto-boxing o se c'è qualche riferimento ad esso nelle specifiche ES .. Leggendo tutta la specifica al momento per controllare, ricorderò di aggiorna la risposta se mai trovo un riferimento.
Fabrício Matté

Ottima conoscenza dell'implementazione del V8. Aggiungo che la boxe non serve solo a risolvere la funzione. È anche lì per passare questo riferimento al metodo. Ora non sono sicuro che V8 salti questo per i metodi incorporati, ma se aggiungi la tua estensione per dire String.prototype otterrai una versione in scatola dell'oggetto stringa ogni volta che viene chiamato.
Ben

17

In caso di stringa letterale non possiamo assegnare proprietà

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

Mentre nel caso di String Object possiamo assegnare proprietà

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

1
Infine qualcuno motiva perché abbiamo bisogno anche di Stringoggetti. Grazie!
Ciprian Tomoiagă

1
Perché qualcuno dovrebbe aver bisogno di farlo?
Aditya

11

Valore letterale stringa:

I letterali stringa sono immutabili, il che significa che, una volta creati, il loro stato non può essere modificato, il che li rende anche thread-safe.

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

a==b il risultato sarà 'vero' entrambe le stringhe fanno riferimento allo stesso oggetto.

Oggetto stringa:

Qui vengono creati due oggetti diversi e hanno riferimenti diversi:

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

a==b il risultato sarà falso, perché hanno riferimenti diversi.


1
Anche l'oggetto stringa è immutabile?
Yang Wang

@YangWang questo è un linguaggio stupido, per entrambi ae btenta di assegnare a[0] = 'X'esso verrà eseguito con successo ma non funzionerà come ci si potrebbe aspettare
Rux

Hai scritto "var a = 's'; var b = 's'; a == b il risultato sarà 'true' entrambe le stringhe fanno riferimento allo stesso oggetto." Non è corretto: aeb non si riferiscono a nessuno stesso oggetto, il risultato è vero perché hanno lo stesso valore. Questi valori sono memorizzati in diverse posizioni di memoria, ecco perché se ne cambi uno l'altro non cambia!
SC1000

9

Se usi new, stai dichiarando esplicitamente che vuoi creare un'istanza di un oggetto . Pertanto, si new Stringsta producendo un oggetto che avvolge la primitiva String , il che significa che qualsiasi azione su di esso comporta un ulteriore livello di lavoro.

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

Poiché sono di diversi tipi, il tuo interprete JavaScript può anche ottimizzarli in modo diverso, come menzionato nei commenti .


5

Quando dichiari:

var s = '0123456789';

crei una primitiva di stringa. Quella primitiva stringa ha metodi che ti consentono di chiamare metodi su di essa senza convertire la primitiva in un oggetto di prima classe. Quindi la tua supposizione che questo sarebbe più lento perché la stringa deve essere convertita in un oggetto non è corretta. Non deve essere convertito in un oggetto. La primitiva stessa può invocare i metodi.

Convertirlo in un oggetto in piena regola (che ti permette di aggiungervi nuove proprietà) è un passo in più e non rende più veloci le esecuzioni delle stringhe (infatti il ​​tuo test mostra che le rende più lente).


Come mai la primitiva stringa eredita tutte le proprietà del prototipo comprese String.prototypequelle personalizzate ?
Yuriy Galanter

1
var s = '0123456789';è un valore primitivo, come può questo valore avere metodi, sono confuso!
L'alfa

2
@SheikhHeera - le primitive sono incorporate nell'implementazione del linguaggio in modo che l'interprete possa conferire loro poteri speciali.
jfriend00

1
@SheikhHeera - Non capisco il tuo ultimo commento / domanda. Una primitiva stringa di per sé non supporta l'aggiunta delle proprie proprietà. Per consentire ciò, javascript ha anche un oggetto String che ha tutti gli stessi metodi di una primitiva stringa, ma è un oggetto in piena regola che puoi trattare come un oggetto in tutti i modi. Questa doppia forma sembra essere un po 'confusa, ma sospetto che sia stata fatta come un compromesso sulle prestazioni poiché il 99% del caso è l'uso di primitive e possono probabilmente essere più veloci ed efficienti in termini di memoria rispetto agli oggetti stringa.
jfriend00

1
@SheikhHeera "convertito in oggetto stringa" è il modo in cui MDN lo esprime per spiegare come le primitive sono in grado di invocare metodi. Non vengono letteralmente convertiti in oggetti stringa.
Fabrício Matté

4

Vedo che questa domanda è stata risolta molto tempo fa, c'è un'altra sottile distinzione tra stringhe letterali e oggetti stringa, poiché nessuno sembra averla toccata, ho pensato di scriverla solo per completezza.

Fondamentalmente un'altra distinzione tra i due è quando si utilizza eval. eval ('1 + 1') restituisce 2, mentre eval (new String ('1 + 1')) restituisce '1 + 1', quindi se un determinato blocco di codice può essere eseguito sia 'normalmente' che con eval, potrebbe portare a risultati strani


Grazie per il tuo contributo :-)
L'Alpha

Wow, questo è un comportamento davvero strano. Dovresti aggiungere una piccola demo in linea nel tuo commento per mostrare questo comportamento: è estremamente aperto agli occhi.
EyuelDK

questo è normale, se ci pensi. new String("")restituisce un oggetto ed eval valuta solo la stringa, et restituisce ogni altra cosa così com'è
Félix Brunet,

3

L'esistenza di un oggetto ha poco a che fare con il comportamento effettivo di una stringa nei motori ECMAScript / JavaScript poiché l'ambito root conterrà semplicemente oggetti funzione per questo. Quindi la funzione charAt (int) nel caso di una stringa letterale verrà cercata ed eseguita.

Con un oggetto reale si aggiunge un altro livello in cui viene cercato anche il metodo charAt (int) sull'oggetto stesso prima che il comportamento standard si attivi (come sopra). Apparentemente c'è una quantità sorprendentemente grande di lavoro svolto in questo caso.

A proposito, non penso che le primitive siano effettivamente convertite in oggetti, ma il motore di script segnerà semplicemente questa variabile come tipo stringa e quindi può trovare tutte le funzioni fornite per esso in modo che sembri che tu invochi un oggetto. Non dimenticare che questo è un runtime di script che funziona su principi diversi rispetto a un runtime OO.


3

La più grande differenza tra una primitiva stringa e un oggetto stringa è che gli oggetti devono seguire questa regola per l' ==operatore :

Un'espressione che confronta gli oggetti è vera solo se gli operandi fanno riferimento allo stesso oggetto.

Quindi, mentre le primitive stringa hanno un comodo ==che confronta il valore, sei sfortunato quando si tratta di fare in modo che qualsiasi altro tipo di oggetto immutabile (incluso un oggetto stringa) si comporti come un tipo di valore.

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

(Altri hanno notato che un oggetto stringa è tecnicamente modificabile perché è possibile aggiungere proprietà ad esso. Ma non è chiaro a cosa sia utile; il valore stringa stesso non è modificabile.)


Grazie per aver aggiunto valore alla domanda dopo un tempo piuttosto lungo :-)
L'Alpha

1

Il codice è ottimizzato prima di essere eseguito dal motore javascript. In generale, i micro benchmark possono essere fuorvianti perché i compilatori e gli interpreti riorganizzano, modificano, rimuovono ed eseguono altri trucchi su parti del codice per renderlo più veloce. In altre parole, il codice scritto dice qual è l'obiettivo, ma il compilatore e / o il runtime decideranno come raggiungerlo.

Il blocco 1 è più veloce principalmente a causa di: var s = '0123456789'; è sempre più veloce di var s = new String ('0123456789'); a causa del sovraccarico della creazione di oggetti.

La porzione di loop non è quella che causa il rallentamento perché chartAt () può essere inline dall'interprete. Prova a rimuovere il loop e riesegui il test, vedrai che il rapporto di velocità sarà lo stesso come se il loop non fosse stato rimosso. In altre parole, per questi test, i blocchi di loop in fase di esecuzione hanno esattamente lo stesso bytecode / codice macchina.

Per questi tipi di micro benchmark, guardare il bytecode o il codice macchina fornirà un'immagine più chiara.


1
Grazie per la tua risposta.
The Alpha

0

In Javascript, i tipi di dati primitivi come la stringa sono un elemento costitutivo non composito. Ciò significa che sono solo valori, niente di più: let a = "string value"; per impostazione predefinita non ci sono metodi integrati come toUpperCase, toLowerCase ecc ...

Ma se provi a scrivere:

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

Questo non genererà alcun errore, invece funzioneranno come dovrebbero.

Quello che è successo ? Bene, quando provi ad accedere a una proprietà di una stringa, aJavascript costringe la stringa a un oggetto new String(a);noto come oggetto wrapper .

Questo processo è collegato a concetti chiamati costruttori di funzioni in Javascript, dove le funzioni vengono utilizzate per creare nuovi oggetti.

Quando digiti new String('String value');qui String è un costruttore di funzioni, che accetta un argomento e crea un oggetto vuoto all'interno dell'ambito della funzione, questo oggetto vuoto viene assegnato a questo e in questo caso, String fornisce tutte quelle funzioni incorporate note che abbiamo menzionato prima. e non appena l'operazione è completata, ad esempio eseguire un'operazione in maiuscolo, l'oggetto wrapper viene scartato.

Per dimostrarlo, facciamo questo:

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

Qui l'output sarà indefinito. Perché ? In questo caso Javascript crea un oggetto String wrapper, imposta la nuova proprietà addNewProperty e scarta immediatamente l'oggetto wrapper. questo è il motivo per cui diventi indefinito. Lo pseudo codice sarebbe simile a questo:

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

0

possiamo definire String in 3 modi

  1. var a = "first way";
  2. var b = String ("seconda via");
  3. var c = nuova stringa ("terza via");

// possiamo anche creare usando 4. var d = a + '';

Verificare il tipo di stringhe create utilizzando l'operatore typeof

  • digitare una // "stringa"
  • tipo di b // "stringa"
  • tipo di c // "oggetto"


quando si confrontano aeb var a==b ( // yes)


quando si confronta l'oggetto String

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