Le javascript utilise-t-il des chaînes immuables ou modifiables? Ai-je besoin d'un "générateur de chaînes"?
Le javascript utilise-t-il des chaînes immuables ou modifiables? Ai-je besoin d'un "générateur de chaînes"?
Réponses:
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
, slice
retournent 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
Array.push
etArray.join
Array.push
, puis utilisationArray.join
Ensuite, j'ai créé les trois mêmes tests en les résumant StringBuilderConcat
, StringBuilderArrayPush
et 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.join
n'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.join
n'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.
join
la 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 for
boucle du join
test non plus).
Array.join
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.
null
undefined
number
et 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.
var a = "hello";var b=a;a.x=5;console.log(a.x,b.x);
String
objets 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 Number
objets et les valeurs numériques. Il est important de noter que les String
objets créés à l'aide new String
ne 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
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 StringBuilder
en a pas en JavaScript.
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:
- La valeur existante de "immutableString" est récupérée
- "World" est ajouté à la valeur existante de "immutableString"
- La valeur résultante est ensuite allouée à un nouveau bloc de mémoire
- L'objet "immutableString" pointe désormais vers l'espace mémoire nouvellement créé
- L'espace mémoire précédemment créé est désormais disponible pour la récupération de place.
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.
new String
génère un wrapper mutable autour d'une chaîne immuable
String
objet (wrapper), ce qui signifie qu'il n'est pas immuable (par défaut; comme tout autre objet, vous pouvez l'appeler Object.freeze
pour le rendre immuable). Mais un type de valeur de chaîne primitive, qu'il soit contenu dans un String
wrapper d'objet ou non, est toujours immuable.
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.
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
Les chaînes JavaScript sont en effet immuables.
Les chaînes en Javascript sont immuables