Quelle est la différence entre les primitives de chaîne et les objets String en JavaScript?

116

Tiré de MDN

Les chaînes littérales (désignées par des guillemets doubles ou simples) et les chaînes renvoyées par les appels String dans un contexte non constructeur (c'est-à-dire sans utiliser le nouveau mot clé) sont des chaînes primitives. JavaScript convertit automatiquement les primitives en objets String, de sorte qu'il est possible d'utiliser des méthodes d'objet String pour les chaînes primitives. Dans les contextes où une méthode doit être appelée sur une chaîne primitive ou une recherche de propriété se produit, JavaScript encapsule automatiquement la chaîne primitive et appelle la méthode ou effectue la recherche de propriété.

Donc, j'ai pensé (logiquement) que les opérations (appels de méthode) sur les primitives de chaîne devraient être plus lentes que les opérations sur les objets de chaîne car toute primitive de chaîne est convertie en objet de chaîne (travail supplémentaire) avant d' methodêtre appliquée sur la chaîne.

Mais dans ce cas de test , le résultat est opposé. Le bloc de code-1 s'exécute plus rapidement que le bloc de code-2 , les deux blocs de code sont donnés ci-dessous:

bloc de code-1:

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

bloc de code-2:

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

Les résultats varient selon les navigateurs mais le code block-1 est toujours plus rapide. Quelqu'un peut-il expliquer ceci, pourquoi le bloc de code 1 est plus rapide que le bloc de code 2 .

L'Alpha
la source
6
L'utilisation new Stringintroduit une autre couche transparente d' habillage d' objets . typeof new String(); //"object"
Paul S.
et quoi '0123456789'.charAt(i)?
Yuriy Galanter
@YuriyGalanter, ce n'est pas un problème mais je demande pourquoi code block-1est plus rapide?
The Alpha
2
Les objets chaîne sont relativement rares à être vus dans un contexte réel, il n'est donc pas surprenant que les interprètes optimisent les littéraux de chaîne. De nos jours, votre code n'est pas simplement interprété , il existe de nombreuses couches d'optimisation qui se produisent dans les coulisses.
Fabrício Matté
2
C'est bizarre: révision 2
hjpotter92

Réponses:

149

JavaScript a deux catégories de types principales, les primivites et les objets.

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

Les modèles de guillemets simples / doubles sont identiques en termes de fonctionnalités. Cela mis à part, le comportement que vous essayez de nommer s'appelle la boxe automatique. Donc, ce qui se passe réellement, c'est qu'une primitive est convertie en son type wrapper lorsqu'une méthode du type wrapper est appelée. En termes simples:

var s = 'test';

Est un type de données primitif. Il n'a pas de méthode, ce n'est rien de plus qu'un pointeur vers une référence mémoire de données brutes, ce qui explique la vitesse d'accès aléatoire beaucoup plus rapide.

Alors, que se passe-t-il lorsque vous faites s.charAt(i)par exemple?

Puisqu'il sn'est pas une instance de String, JavaScript encapsulera automatiquement s, qui doit typeof stringson type de wrapper,, Stringavec typeof objectou plus précisément s.valueOf(s).prototype.toString.call = [object String].

Le comportement de mise en boîte automatique effectue un va- set-vient vers son type d'enveloppe selon les besoins, mais les opérations standard sont incroyablement rapides car vous avez affaire à un type de données plus simple. Cependant l'auto-boxe etObject.prototype.valueOf ont des effets différents.

Si vous souhaitez forcer l'auto-boxing ou convertir une primitive en son type d'enveloppe, vous pouvez utiliser Object.prototype.valueOf, mais le comportement est différent. Sur la base d'une grande variété de scénarios de test, l'auto-boxing n'applique que les méthodes «requises», sans altérer la nature primitive de la variable. C'est pourquoi vous obtenez une meilleure vitesse.

flavien
la source
33

Cela dépend plutôt de la mise en œuvre, mais je vais essayer. Je vais illustrer avec V8 mais je suppose que d'autres moteurs utilisent des approches similaires.

Une primitive de chaîne est analysée en un v8::Stringobjet. Par conséquent, les méthodes peuvent être invoquées directement dessus comme mentionné par jfriend00 .

Un objet String, en revanche, est analysé en un v8::StringObjectqui s'étend Objectet, en plus d'être un objet à part entière, sert de wrapper pour v8::String.

Maintenant , il est logique, un appel à new String('').method()doit unbox v8::StringObjectc'est v8::Stringavant d' exécuter la méthode, il est donc plus lent.


Dans de nombreux autres langages, les valeurs primitives n'ont pas de méthodes.

La façon dont MDN le dit semble être le moyen le plus simple d'expliquer comment fonctionne l'auto-boxing des primitives (comme également mentionné dans la réponse de flav ), c'est-à-dire comment le primitif-y de JavaScript valeurs peuvent invoquer des méthodes.

Cependant, un moteur intelligent ne convertira pas une chaîne primitive-y en objet String à chaque fois que vous devez appeler une méthode. Ceci est également mentionné de manière informative dans la spécification ES5 annotée. en ce qui concerne la résolution des propriétés (et des "méthodes" ¹) des valeurs primitives:

REMARQUE L'objet qui peut être créé à l'étape 1 n'est pas accessible en dehors de la méthode ci-dessus. Une implémentation peut choisir d'éviter la création réelle de l'objet. [...]

À très bas niveau, les chaînes sont le plus souvent implémentées en tant que valeurs scalaires immuables. Exemple de structure de wrapper:

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

Plus vous êtes loin du primitif, plus il faudra de temps pour y arriver. En pratique, les Stringprimitives sont beaucoup plus fréquentes que StringObjects, il n'est donc pas surprenant que les moteurs ajoutent des méthodes à la classe des objets correspondants (interprétés) des primitives String au lieu de faire des va-et-vient entre Stringet StringObjectcomme le suggère l'explication de MDN.


¹ En JavaScript, «méthode» est simplement une convention de dénomination pour une propriété qui se résout en une valeur de type fonction.

Fabrício Matté
la source
1
De rien. =]Maintenant, je me demande si l'explication de MDN est là simplement parce que cela semble être le moyen le plus simple de comprendre l'auto-boxing ou s'il y a une référence à celui-ci dans la spécification ES. mettre à jour la réponse si jamais je trouve une référence.
Fabrício Matté
Excellent aperçu de la mise en œuvre du V8. J'ajouterai que la boxe n'est pas seulement là pour résoudre la fonction. Il est également là pour passer cette référence à la méthode. Maintenant, je ne sais pas si V8 ignore cela pour les méthodes intégrées, mais si vous ajoutez votre propre extension pour dire String.prototype, vous obtiendrez une version encadrée de l'objet string à chaque fois qu'il est appelé.
Ben
17

En cas de littéral de chaîne, nous ne pouvons pas attribuer de propriétés

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

Alors que dans le cas de String Object, nous pouvons attribuer des propriétés

var x = new String("hello");
x.y = "world";
console.log(x.y); // this will print world
refactor
la source
1
Enfin, quelqu'un explique pourquoi nous avons également besoin d' Stringobjets. Je vous remercie!
Ciprian Tomoiagă
1
Pourquoi quelqu'un aurait-il besoin de faire cela?
Aditya
11

Chaîne littérale:

Les littéraux de chaîne sont immuables, ce qui signifie qu'une fois qu'ils sont créés, leur état ne peut pas être modifié, ce qui les rend également sûrs pour les threads.

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

a==b le résultat sera «vrai» le même objet pour les deux chaînes.

Objet chaîne:

Ici, deux objets différents sont créés, et ils ont des références différentes:

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

a==b Le résultat sera faux, car ils ont des références différentes.

Wajahat Ali Qureshi
la source
1
L'objet string est-il également immuable?
Yang Wang
@YangWang c'est un langage idiot, pour les deux aet bessayez de l'assigner, a[0] = 'X'il sera exécuté avec succès mais ne fonctionnera pas comme vous pourriez vous y attendre
ruX
Vous avez écrit "var a = 's'; var b = 's'; a == b résultat sera 'true' les deux chaînes font référence au même objet." Ce n'est pas correct: a et b ne font référence à aucun même objet, le résultat est vrai car ils ont la même valeur. Ces valeurs sont stockées dans différents emplacements de mémoire, c'est pourquoi si vous changez l'une, l'autre ne change pas!
SC1000
9

Si vous utilisez new, vous indiquez explicitement que vous souhaitez créer une instance d'un Object . Par conséquent, new Stringproduit un objet encapsulant la primitive String , ce qui signifie que toute action sur celui-ci implique une couche supplémentaire de travail.

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

Comme ils sont de types différents, votre interpréteur JavaScript peut également les optimiser différemment, comme mentionné dans les commentaires .

Paul S.
la source
5

Lorsque vous déclarez:

var s = '0123456789';

vous créez une chaîne primitive. Cette primitive de chaîne a des méthodes qui vous permettent d'appeler des méthodes dessus sans convertir la primitive en un objet de première classe. Donc, votre supposition que ce serait plus lent parce que la chaîne doit être convertie en objet n'est pas correcte. Il n'a pas besoin d'être converti en objet. La primitive elle-même peut invoquer les méthodes.

Le convertir en un objet à part entière (qui vous permet d'y ajouter de nouvelles propriétés) est une étape supplémentaire et ne rend pas les opérations de la chaîne plus rapides (en fait, votre test montre que cela les ralentit).

jfriend00
la source
Comment se fait-il que la primitive de chaîne hérite de toutes les propriétés du prototype, y compris les propriétés personnalisées String.prototype?
Yuriy Galanter
1
var s = '0123456789';est une valeur primitive, comment cette valeur peut-elle avoir des méthodes, je suis confus!
The Alpha
2
@SheikhHeera - les primitives sont intégrées à l'implémentation du langage afin que l'interpréteur puisse leur donner des pouvoirs spéciaux.
jfriend00
1
@SheikhHeera - Je ne comprends pas votre dernier commentaire / question. Une primitive de chaîne en elle-même ne prend pas en charge l'ajout de vos propres propriétés. Afin de permettre cela, javascript a également un objet String qui a toutes les mêmes méthodes qu'une chaîne primitive, mais est un objet à part entière que vous pouvez traiter comme un objet de toutes les manières. Cette double forme semble être un peu désordonnée, mais je suppose que cela a été fait comme un compromis de performance puisque le cas à 99% est l'utilisation de primitives et ils peuvent probablement être plus rapides et plus efficaces en mémoire que les objets chaîne.
jfriend00
1
@SheikhHeera "converti en objet string" est la façon dont MDN l'exprime pour expliquer comment les primitives peuvent invoquer des méthodes. Ils ne sont pas littéralement convertis en objets de chaîne.
Fabrício Matté
4

Je peux voir que cette question a été résolue il y a longtemps, il y a une autre distinction subtile entre les littéraux de chaîne et les objets de chaîne, comme personne ne semble l'avoir touché, j'ai pensé que je l'écrirais juste pour être complet.

Fondamentalement, une autre distinction entre les deux est lors de l'utilisation de eval. eval ('1 + 1') donne 2, alors que eval (new String ('1 + 1')) donne '1 + 1', donc si un certain bloc de code peut être exécuté à la fois 'normalement' ou avec eval, il pourrait conduire à des résultats étranges

luanped
la source
Merci pour votre contribution :-)
The Alpha
Wow, c'est un comportement vraiment étrange. Vous devriez ajouter une petite démo en ligne dans votre commentaire pour montrer ce comportement - c'est extrêmement révélateur.
EyuelDK
c'est normal, si vous y réfléchissez. new String("")retourne un objet, et eval n'évalue que la chaîne, et retourne tout le reste tel quel
Félix Brunet
3

L'existence d'un objet a peu à voir avec le comportement réel d'une chaîne dans les moteurs ECMAScript / JavaScript car la portée racine contiendra simplement des objets fonction pour cela. Ainsi, la fonction charAt (int) dans le cas d'un littéral de chaîne sera recherchée et exécutée.

Avec un objet réel, vous ajoutez un autre calque où la méthode charAt (int) est également recherchée sur l'objet lui-même avant que le comportement standard ne démarre (comme ci-dessus). Apparemment, il y a un travail étonnamment important dans ce cas.

BTW Je ne pense pas que les primitives soient réellement converties en objets mais le moteur de script marquera simplement cette variable comme type de chaîne et par conséquent, il peut trouver toutes les fonctions fournies pour cela, il semble que vous invoquiez un objet. N'oubliez pas qu'il s'agit d'un runtime de script qui fonctionne sur des principes différents de celui d'un runtime OO.

eau claire
la source
3

La plus grande différence entre une chaîne primitive et un objet chaîne est que les objets doivent suivre cette règle pour l' ==opérateur :

Une expression comparant des objets n'est vraie que si les opérandes font référence au même objet.

Ainsi, alors que les primitives de chaîne ont une fonction pratique ==qui compare la valeur, vous n'avez pas de chance quand il s'agit de faire en sorte que tout autre type d'objet immuable (y compris un objet de chaîne) se comporte comme un type valeur.

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

(D'autres ont noté qu'un objet chaîne est techniquement modifiable car vous pouvez lui ajouter des propriétés. Mais on ne sait pas à quoi cela sert; la valeur de chaîne elle-même n'est pas modifiable.)

personal_cloud
la source
Merci d'avoir ajouté de la valeur à la question après un certain temps :-)
The Alpha
1

Le code est optimisé avant d'être exécuté par le moteur javascript. En général, les micro-tests peuvent être trompeurs car les compilateurs et les interprètes réorganisent, modifient, suppriment et exécutent d'autres astuces sur des parties de votre code pour le rendre plus rapide. En d'autres termes, le code écrit indique quel est l'objectif, mais le compilateur et / ou le moteur d'exécution décideront comment atteindre cet objectif.

Le bloc 1 est plus rapide principalement à cause de: var s = '0123456789'; est toujours plus rapide que var s = new String ('0123456789'); en raison de la surcharge de la création d'objets.

La portion de boucle n'est pas celle qui cause le ralentissement car le chartAt () peut être inséré par l'interpréteur. Essayez de supprimer la boucle et relancez le test, vous verrez que le rapport de vitesse sera le même que si la boucle n'avait pas été supprimée. En d'autres termes, pour ces tests, les blocs de boucle au moment de l'exécution ont exactement le même bytecode / code machine.

Pour ces types de micro-benchmarks, regarder le bytecode ou le code machine fournira une image plus claire.

Arc
la source
1
Merci pour votre réponse.
The Alpha
0

En Javascript, les types de données primitifs tels que string sont un bloc de construction non composite. Cela signifie que ce ne sont que des valeurs, rien de plus: let a = "string value"; par défaut, il n'y a pas de méthodes intégrées comme toUpperCase, toLowerCase etc ...

Mais, si vous essayez d'écrire:

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

Cela ne générera aucune erreur, mais ils fonctionneront comme ils le devraient.

Qu'est-il arrivé ? Eh bien, lorsque vous essayez d'accéder à une propriété d'une chaîne, aJavascript contraint la chaîne à un objet new String(a);appelé objet wrapper .

Ce processus est lié au concept appelé constructeurs de fonctions en Javascript, où les fonctions sont utilisées pour créer de nouveaux objets.

Lorsque vous tapez new String('String value');ici String est un constructeur de fonction, qui prend un argument et crée un objet vide à l'intérieur de la portée de la fonction, cet objet vide est affecté à ce et dans ce cas, String fournit toutes celles connues fonctions intégrées dont nous avons parlé auparavant. et dès que l'opération est terminée, par exemple faire une opération en majuscules, l'objet wrapper est rejeté.

Pour le prouver, faisons ceci:

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

Ici, la sortie ne sera pas définie. Pourquoi ? Dans ce cas, Javascript crée un objet String wrapper, définit une nouvelle propriété addNewProperty et rejette immédiatement l'objet wrapper. c'est pourquoi vous êtes indéfini. Le pseudo code ressemblerait à ceci:

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

nous pouvons définir String de 3 façons

  1. var a = "première voie";
  2. var b = String ("deuxième voie");
  3. var c = new String ("troisième voie");

// nous pouvons également créer en utilisant 4. var d = a + '';

Vérifiez le type des chaînes créées à l'aide de l'opérateur typeof

  • type d'une // "chaîne"
  • typeof b // "chaîne"
  • typeof c // "objet"


quand on compare a et b var a==b ( // yes)


lorsque vous comparez un objet 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
SouMitya chauhan
la source