web-dev-qa-db-fra.com

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

Tiré de MDN

Les littéraux de chaîne (indiqués par des guillemets simples ou doubles) et les chaînes renvoyées à partir d'appels de chaîne 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é est effectuée, JavaScript encapsule automatiquement la primitive de chaîne 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 sur les objets de chaîne, car toute primitive de chaîne est convertie en chaîne Object (travail supplémentaire) avant que le method ne soit appliqué sur la chaîne.

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

code bloc-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 s'il vous plaît expliquer cela, pourquoi le code bloc-1 est plus rapide que code bloc-2 .

99
The Alpha

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

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

Les modèles de guillemets simples/doubles sont identiques en termes de fonctionnalité. Cela dit, 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. Mettez simple:

var s = 'test';

Est un type de données primitif. Il n'a pas de méthode, ce n'est rien d'autre qu'un pointeur sur une référence de 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?

Puisque s n’est pas une instance de String, JavaScript encapsulera automatiquement s, qui a typeof string Pour son type d’enveloppe, String , avec typeof object ou plus précisément s.valueOf(s).prototype.toString.call = [object String].

Le comportement de boxe automatique jette s en avant et en arrière dans son type d'encapsuleur si nécessaire, mais les opérations standard sont incroyablement rapides, car vous avez affaire à un type de données plus simple. Cependant, la boxe automatique et Object.prototype.valueOf Ont des effets différents.

Si vous voulez forcer la boîte automatique ou attribuer une primitive à son type enveloppe, vous pouvez utiliser Object.prototype.valueOf, Mais le comportement est différent. Basé sur une grande variété de scénarios de test, la boxe automatique applique uniquement les méthodes "requises", sans modifier la nature primitive de la variable. C'est pourquoi vous obtenez une meilleure vitesse.

129
flavian

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 dans un objet v8::String . Par conséquent, les méthodes peuvent être invoquées directement dessus comme mentionné par jfriend.

D'autre part, un objet String est analysé avec un v8::StringObject qui étend Object et, en plus d'être un objet à part entière, sert d'enveloppe à v8::String.

Maintenant, ce n’est que logique, un appel à new String('').method() doit déballer ce v8::StringObjectv8::String Avant d’exécuter la méthode, elle est donc plus lente.


Dans beaucoup d'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 le fonctionnement de la box-boxing automatique des primitives (comme mentionné également dans la réponse de flav), c'est-à-dire comment JavaScript primitive-y les 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é à titre informatif dans le Spéc. Annotée ES5 en ce qui concerne les propriétés de résolution (et les "méthodes" ¹) des valeurs primitives:

[~ # ~] note [~ # ~] L'objet pouvant ê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. [...]

A un niveau très bas, les chaînes sont le plus souvent implémentées sous forme de valeurs scalaires immuables. Exemple de structure d'emballage:

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

Plus vous êtes éloigné du primitif, plus il vous faudra du temps pour y arriver. Dans la pratique, les primitives String sont beaucoup plus fréquentes que StringObjects; il n’est donc pas surprenant que les moteurs ajoutent des méthodes à la classe Class des objets (interprétés) des primitives String au lieu de convertir entre String et StringObject comme l'explique 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.

31
Fabrício Matté

En cas de littéral, nous ne pouvons pas affecter de propriétés

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

Considérant que dans le cas d'un objet String, nous pouvons attribuer des propriétés

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

Si vous utilisez new, vous indiquez explicitement que vous souhaitez créer une instance d'un objet . Donc, new String produit un objet qui enveloppe la primitive String , ce qui signifie son action implique une couche de travail supplémentaire.

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

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

9
Paul S.

String Literal:

Les littéraux de chaîne sont immuables, ce qui signifie qu'une fois créés, leur état ne peut pas être modifié, ce qui les rend également thread-safe.

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

a==b résultat sera 'vrai' les deux chaînes font référence au même objet.

Objet String:

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 résultat sera faux, car ils ont des références différentes.

9

Lorsque vous déclarez:

var s = '0123456789';

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

La convertir en un objet à part entière (ce qui vous permet de lui ajouter de nouvelles propriétés) est une étape supplémentaire qui ne permet pas d'accélérer les interprétations de chaînes (en fait, votre test montre qu'elles les ralentissent).

5
jfriend00

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 prenne effet (comme ci-dessus). Apparemment, il y a une quantité de travail étonnamment grande dans ce cas.

En passant, je ne pense pas que les primitives soient réellement converties en objets, mais le moteur de script marquera simplement cette variable en tant que type de chaîne. Par conséquent, il peut trouver toutes les fonctions fournies pour qu'elle ressemble à l'invocation d'un objet. N'oubliez pas qu'il s'agit d'un runtime de script qui fonctionne sur des principes différents de ceux d'un OO runtime.

3
clearwater

Je peux voir que cette question a été résolue il y a longtemps. Il existe une autre distinction subtile entre les littéraux de chaîne et les objets chaîne. Personne ne semble l'avoir abordée. Je pensais que je devrais l'écrire pour l'exhaustivité.

Une autre distinction entre les deux concerne l’utilisation de eval. eval ('1 + 1') donne 2, alors que eval (new String ('1 + 1')) donne '1 + 1'. Ainsi, si certains blocs de code peuvent être exécutés à la fois 'normalement' ou avec eval, conduire à des résultats étranges

3
luanped

La plus grande différence entre une primitive de chaîne et un objet de chaîne est que les objets doivent suivre cette règle pour le == 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 un == qui compare la valeur, vous n’avez pas de chance de faire en sorte que tout autre type d’objet immuable (y compris un objet chaîne) se comporte comme un type de valeur.

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

(D'autres personnes ont noté qu'un objet de chaîne est techniquement mutable parce que vous pouvez lui ajouter des propriétés. Mais on ne sait pas à quoi ça sert, la valeur de chaîne elle-même n'est pas mutable.)

1
personal_cloud