Les chaînes JavaScript sont-elles immuables? Ai-je besoin d'un "générateur de chaînes" en JavaScript?

237

Le javascript utilise-t-il des chaînes immuables ou modifiables? Ai-je besoin d'un "générateur de chaînes"?

DevelopingChris
la source
3
Oui, les y sont immuables et vous avez besoin d'un "générateur de chaînes". Lisez ce blog.codeeffects.com/Article/String-Builder-In-Java-Script ou ce codeproject.com/KB/scripting/stringbuilder.aspx
Kizz
2
Intéressant, ces exemples contredisent mes conclusions dans ma réponse.
Juan Mendes

Réponses:

294

Ils sont immuables. Vous ne pouvez pas changer un caractère dans une chaîne avec quelque chose comme var myString = "abbdef"; myString[2] = 'c'. Les méthodes de manipulation de chaînes telles que trim, sliceretournent de nouvelles chaînes.

De la même manière, si vous avez deux références à la même chaîne, la modification de l'une n'affecte pas l'autre

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

Cependant, j'ai toujours entendu ce que Ash a mentionné dans sa réponse (que l'utilisation de Array.join est plus rapide pour la concaténation), donc je voulais tester les différentes méthodes de concaténation de chaînes et d'abstraire le moyen le plus rapide dans un StringBuilder. J'ai écrit quelques tests pour voir si c'est vrai (ce n'est pas le cas!).

C'est ce que je pensais être le moyen le plus rapide, même si je continuais à penser que l'ajout d'un appel de méthode pouvait le ralentir ...

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

Voici des tests de vitesse de performance. Tous les trois créent une chaîne gigantesque composée de concaténation "Hello diggity dog"cent mille fois en une chaîne vide.

J'ai créé trois types de tests

  • Utilisation de Array.pushetArray.join
  • Utilisation de l'indexation de tableau pour éviter Array.push, puis utilisationArray.join
  • Concaténation de cordes droites

Ensuite, j'ai créé les trois mêmes tests en les résumant StringBuilderConcat, StringBuilderArrayPushet StringBuilderArrayIndex http://jsperf.com/string-concat-without-sringbuilder/5 Veuillez y aller et exécuter des tests afin que nous puissions obtenir un bel échantillon. Notez que j'ai corrigé un petit bug, donc les données pour les tests ont été effacées, je mettrai à jour le tableau une fois qu'il y aura suffisamment de données de performances. Accédez à http://jsperf.com/string-concat-without-sringbuilder/5 pour l'ancien tableau de données.

Voici quelques chiffres (dernière mise à jour du Ma5rch 2018), si vous ne souhaitez pas suivre le lien. Le nombre sur chaque test est en 1000 opérations / seconde ( plus c'est mieux )

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

Résultats

  • De nos jours, tous les navigateurs à feuilles persistantes gèrent bien la concaténation de chaînes. Array.joinn'aide que IE 11

  • Dans l'ensemble, Opera est le plus rapide, 4 fois plus rapide que Array.join

  • Firefox est deuxième et Array.joinn'est que légèrement plus lent dans FF mais considérablement plus lent (3x) dans Chrome.

  • Chrome est le troisième, mais la concaténation des chaînes est 3 fois plus rapide que Array.join

  • La création d'un StringBuilder ne semble pas trop affecter les performances.

J'espère que quelqu'un d'autre trouvera cela utile

Cas de test différent

Puisque @RoyTinker pensait que mon test était défectueux, j'ai créé un nouveau cas qui ne crée pas une grosse chaîne en concaténant la même chaîne, il utilise un caractère différent pour chaque itération. La concaténation de chaînes semblait toujours plus rapide ou tout aussi rapide. Lançons ces tests.

Je suggère à tout le monde de continuer à penser à d'autres façons de tester cela, et n'hésitez pas à ajouter de nouveaux liens vers différents cas de test ci-dessous.

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

Juan Mendes
la source
@Juan, le lien que vous nous avez demandé de visiter concatène une chaîne de 112 caractères 30 fois. Voici un autre test qui pourrait aider à équilibrer les choses - Array.join vs concaténation de chaînes sur 20 000 chaînes de 1 caractère différentes (la jointure est beaucoup plus rapide sur IE / FF). jsperf.com/join-vs-str-concat-large-array
Roy Tinker
1
@RoyTinker Roy, oh Roy, vos tests trichent parce que vous créez le tableau dans la configuration du test. Voici le vrai test en utilisant différents caractères jsperf.com/string-concat-without-sringbuilder/7 N'hésitez pas à créer de nouveaux cas de test, mais la création du tableau fait partie du test lui
Juan Mendes
@JuanMendes Mon objectif était de restreindre le cas de test à une comparaison stricte de joinla concaténation vs chaîne, donc de construire le tableau avant le test. Je ne pense pas que ce soit de la triche si cet objectif est compris (et joinénumère le tableau en interne, donc ce n'est pas de la triche d'omettre une forboucle du jointest non plus).
Roy Tinker
2
@RoyTinker Oui, tout générateur de chaînes nécessitera la construction du tableau. La question est de savoir si un générateur de chaînes est nécessaire. Si vous avez déjà les chaînes dans un tableau, ce n'est pas un cas de test valide pour ce dont nous discutons ici
Juan Mendes
1
@Baltusaj Les colonnes qui disent Index / Push utilisentArray.join
Juan Mendes
41

extrait du livre sur les rhinocéros :

En JavaScript, les chaînes sont des objets immuables, ce qui signifie que les caractères qu'ils contiennent ne peuvent pas être modifiés et que toutes les opérations sur les chaînes créent en fait de nouvelles chaînes. Les chaînes sont attribuées par référence et non par valeur. En général, lorsqu'un objet est affecté par référence, une modification apportée à l'objet via une référence sera visible à travers toutes les autres références à l'objet. Cependant, comme les chaînes ne peuvent pas être modifiées, vous pouvez avoir plusieurs références à un objet chaîne et ne pas craindre que la valeur de la chaîne change à votre insu.

mentholé
la source
7
Lien vers une section appropriée du livre sur les rhinocéros: books.google.com/…
baudtack
116
La citation du livre Rhino (et donc cette réponse) est fausse ici. En JavaScript, les chaînes sont des types de valeur primitifs et non des objets (spécification) . En fait, depuis ES5, ils sont l'un des 5 seuls types de valeur aux côtés de null undefined numberet boolean. Les chaînes sont attribuées par valeur et non par référence et sont transmises comme telles. Ainsi, les chaînes ne sont pas seulement immuables, elles sont une valeur . Changer la chaîne "hello"pour qu'elle soit"world" c'est comme décider que désormais le numéro 3 est le numéro 4 ... cela n'a aucun sens.
Benjamin Gruenbaum
8
Oui, comme mon commentaire dit que les chaînes sont immuables, mais ce ne sont pas des types de référence ni des objets - ce sont des types de valeur primitifs. Un moyen facile de voir qu'ils ne sont ni l'un ni l'autre serait d'essayer d'ajouter une propriété à une chaîne, puis de la lire:var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
Benjamin Gruenbaum
9
@ VidarS.Ramdal Non, les Stringobjets créés à l'aide du constructeur de chaîne sont des wrappers autour des valeurs de chaîne JavaScript. Vous pouvez accéder à la valeur de chaîne du type encadré à l'aide de la .valueOf()fonction - cela est également vrai pour les Numberobjets et les valeurs numériques. Il est important de noter que les Stringobjets créés à l'aide new Stringne sont pas de véritables chaînes mais sont des wrappers ou des boîtes autour des chaînes. Voir es5.github.io/#x15.5.2.1 . Pour savoir comment les choses se convertissent en objets, voir es5.github.io/#x9.9
Benjamin Gruenbaum
5
Quant à savoir pourquoi certaines personnes disent que les chaînes sont des objets, elles proviennent probablement de Python ou Lisp ou de tout autre langage où sa spécification utilise le mot "objet" pour désigner tout type de donnée (même des entiers). Il leur suffit de lire comment la spécification ECMA définit le mot: "membre du type Object". De plus, même le mot "valeur" peut signifier différentes choses selon les spécifications des différentes langues.
Jisang Yoo
21

Conseil de performance:

Si vous devez concaténer de grandes chaînes, placez les parties de chaîne dans un tableau et utilisez la Array.Join()méthode pour obtenir la chaîne globale. Cela peut être plusieurs fois plus rapide pour concaténer un grand nombre de chaînes.

Il n'y StringBuilderen a pas en JavaScript.

Cendre
la source
Je sais qu'il n'y a pas de stringBuilder, msAjax en a un, et je me demandais simplement si c'était utile
DevelopingChris
5
Qu'est-ce que cela a à voir avec les chaînes immuables ou non?
baudtack
4
@docgnome: Parce que les chaînes sont immuables, la concaténation de chaînes nécessite de créer plus d'objets que l'approche Array.join
Juan Mendes
8
Selon le test de Juan ci-dessus, la concaténation de chaînes est en fait plus rapide dans IE et Chrome, tout en étant plus lente dans Firefox.
Bill Yang
9
Pensez à mettre à jour votre réponse, cela était peut-être vrai il y a longtemps, mais ce n'est plus le cas. Voir jsperf.com/string-concat-without-sringbuilder/5
Juan Mendes
8

Juste pour clarifier pour les esprits simples comme le mien (de MDN ):

Les objets immuables sont les objets dont l'état ne peut pas être modifié une fois l'objet créé.

La chaîne et les nombres sont immuables.

Immuable signifie que:

Vous pouvez faire pointer un nom de variable vers une nouvelle valeur, mais la valeur précédente est toujours conservée en mémoire. D'où la nécessité d'un ramassage des ordures.

var immutableString = "Hello";

// Dans le code ci-dessus, un nouvel objet avec une valeur de chaîne est créé.

immutableString = immutableString + "World";

// Nous ajoutons maintenant "World" à la valeur existante.

Il semble que nous mutions la chaîne «immutableString», mais ce n'est pas le cas. Au lieu:

Lorsque vous ajoutez "immutableString" à une valeur de chaîne, les événements suivants se produisent:

  1. La valeur existante de "immutableString" est récupérée
  2. "World" est ajouté à la valeur existante de "immutableString"
  3. La valeur résultante est ensuite allouée à un nouveau bloc de mémoire
  4. L'objet "immutableString" pointe désormais vers l'espace mémoire nouvellement créé
  5. L'espace mémoire précédemment créé est désormais disponible pour la récupération de place.
Katinka Hesselink
la source
4

La valeur du type de chaîne est immuable, mais l'objet String, qui est créé à l'aide du constructeur String (), est modifiable, car il s'agit d'un objet et vous pouvez lui ajouter de nouvelles propriétés.

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

Pendant ce temps, bien que vous puissiez ajouter de nouvelles propriétés, vous ne pouvez pas modifier les propriétés déjà existantes

Une capture d'écran d'un test dans la console Chrome

En conclusion, 1. toutes les valeurs de type chaîne (type primitif) sont immuables. 2. L'objet String est modifiable, mais la valeur de type chaîne (type primitif) qu'il contient est immuable.

zhanziyang
la source
Les objets de chaîne Javascript sont immuables developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
prasun
@prasun mais dans cette page, il est dit: "Tous les types sauf les objets définissent des valeurs immuables (valeurs qui ne peuvent pas être modifiées)." Les objets chaîne sont des objets. Et comment est-il immuable si vous pouvez y ajouter de nouvelles propriétés?
zhanziyang
lire la section "Type de chaîne". Le lien String de Javascript pointe à la fois sur primitive et Object et plus tard, il dit "les chaînes JavaScript sont immuables". Il semble que le document ne soit pas clair sur ce sujet car il est en conflit avec deux notes différentes
prasun
6
new Stringgénère un wrapper mutable autour d'une chaîne immuable
tomdemuyt
1
Il est très facile de tester en exécutant le code de @ zhanziyang ci-dessus. Vous pouvez totalement ajouter de nouvelles propriétés à un Stringobjet (wrapper), ce qui signifie qu'il n'est pas immuable (par défaut; comme tout autre objet, vous pouvez l'appeler Object.freezepour le rendre immuable). Mais un type de valeur de chaîne primitive, qu'il soit contenu dans un Stringwrapper d'objet ou non, est toujours immuable.
Mark Reed
3

Les cordes sont immuables - elles ne peuvent pas changer, nous ne pouvons que créer de nouvelles cordes.

Exemple:

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

var other= statement.slice(2, 10); // new string
GibboK
la source
1

En ce qui concerne votre question (dans votre commentaire à la réponse d'Ash) sur le StringBuilder dans ASP.NET Ajax, les experts semblent en désaccord sur celui-ci.

Christian Wenz dit dans son livre Programming ASP.NET AJAX (O'Reilly) que "cette approche n'a pas d'effet mesurable sur la mémoire (en fait, l'implémentation semble être plus lente que l'approche standard)".

D'un autre côté, Gallo et al disent dans leur livre ASP.NET AJAX en action (Manning) que "Lorsque le nombre de chaînes à concaténer est plus grand, le générateur de chaînes devient un objet essentiel pour éviter d'énormes baisses de performances."

Je suppose que vous auriez besoin de faire votre propre analyse comparative et les résultats pourraient également différer entre les navigateurs. Cependant, même s'il n'améliore pas les performances, il peut toujours être considéré comme "utile" pour les programmeurs habitués à coder avec StringBuilders dans des langages comme C # ou Java.

BirgerH
la source
1

C'est un post tardif, mais je n'ai pas trouvé de bonne citation de livre parmi les réponses.

Voici un certain sauf dans un livre fiable:

Les chaînes sont immuables dans ECMAScript, ce qui signifie qu'une fois qu'elles sont créées, leurs valeurs ne peuvent pas changer. Pour changer la chaîne détenue par une variable, la chaîne d'origine doit être détruite et la variable remplie avec une autre chaîne contenant une nouvelle valeur ... —Professional JavaScript for Web Developers, 3e éd., P.43

Maintenant, la réponse qui cite l'extrait du livre de Rhino est juste au sujet de l'immuabilité des chaînes, mais elle a tort de dire "les chaînes sont assignées par référence, pas par valeur". (ils voulaient probablement à l'origine mettre les mots dans un sens opposé).

L'idée fausse "référence / valeur" est clarifiée dans le chapitre "JavaScript professionnel", intitulé "Valeurs primitives et de référence":

Les cinq types primitifs ... [sont]: non défini, nul, booléen, nombre et chaîne. On dit que ces variables sont accessibles par valeur, car vous manipulez la valeur réelle stockée dans la variable. —Professional JavaScript for Web Developers, 3e éd., P.85

c'est opposé aux objets :

Lorsque vous manipulez un objet, vous travaillez vraiment sur une référence à cet objet plutôt que sur l'objet lui-même. Pour cette raison, ces valeurs seraient accessibles par référence. —Professional JavaScript for Web Developers, 3e éd., P.85

revelt
la source
FWIW: Le livre de Rhino signifie probablement qu'en interne / l'implémentation d' une affectation de chaîne stocke / copie un pointeur (plutôt que de copier le contenu de la chaîne). Cela ne ressemble pas à un accident de leur part, d'après le texte qui suit. Mais je suis d'accord: ils abusent du terme "par référence". Ce n'est pas "par référence" juste parce que l'implémentation passe des pointeurs (pour les performances). Wiki - stratégie d'évaluation est une lecture intéressante sur ce sujet.
ToolmakerSteve
-2

Les chaînes en Javascript sont immuables

Glenn Slaven
la source