Ajouter un élément à un tableau semble anodin. Si ce tableau a été transmis à trois endroits du programme, les trois voient le changement, sans avoir rien demandé.
C'est le propre de la mutation : elle ne produit pas de nouvelle valeur, elle modifie celle qui existe déjà, y compris pour tous ceux qui la détenaient.
Définition
Une mutation est une modification appliquée sur place à une valeur existante. Seuls les objets et les tableaux peuvent muter, parce qu'ils sont manipulés par référence. Les nombres et les chaînes, eux, ne changent jamais.
const original = [3, 1, 2];
const copieSupposee = original;
copieSupposee.push(4);
console.log(original); // [ 3, 1, 2, 4 ]
const vraieCopie = [...original];
vraieCopie.push(5);
console.log(original.length); // 4
console.log(vraieCopie.length); // 5copieSupposee n'est pas une copie : c'est un second nom pour le même tableau. Le Spread (...) en fabrique un vrai, indépendant.
Les méthodes de tableau, rangées en deux camps
La confusion vient de ce que les deux familles se ressemblent à l'écriture. Ce tableau à deux colonnes les sépare.
| Modifient le tableau | Renvoient un nouveau tableau |
|---|---|
push, pop, shift, unshift | concat, slice |
splice | filter, toSpliced |
sort | toSorted |
reverse | toReversed |
Les trois méthodes commençant par to ont justement été ajoutées au langage pour offrir une version qui ne mute pas. Voir Array.sort() et slice() pour le détail de chacune.
Quand muter reste le bon choix
- Sur une valeur locale. Un tableau construit dans une fonction et jamais sorti ne risque rien.
- Sur de très gros volumes. Recopier un million d'éléments à chaque étape finit par se voir.
- Dans un accumulateur explicite. Un Array.reduce() qui remplit son propre objet reste lisible et sûr.
La règle pratique tient en une phrase : mutez ce que vous avez créé, jamais ce que vous avez reçu. Un argument modifié est un Effet de bord que l'appelant n'a pas vu venir.
Questions fréquentes
Comment repérer une mutation qui traîne ?
Le symptôme classique est une valeur qui change sans qu'aucune ligne visible ne la modifie. Geler la structure avec Object.freeze() pendant le développement fait remonter le coupable, puisque la modification lève alors une erreur au lieu de passer. Voir Immuabilité pour les limites du gel.
Une chaîne de caractères peut-elle muter ?
Non. Toutes les méthodes de String (chaîne de caractères) renvoient une nouvelle chaîne, y compris celles qui semblent transformer l'original. Écrire texte.toUpperCase() sans récupérer le résultat ne change rien. La même remarque vaut pour les nombres et les booléens.
Pourquoi mon tri modifie-t-il le tableau d'origine ?
Parce que sort() trie sur place et renvoie le même tableau, pas une copie. C'est le piège le plus fréquent de la famille, surtout quand le résultat est affecté à une nouvelle variable, ce qui donne l'illusion d'une copie. Utilisez toSorted(), ou triez une copie obtenue avec [...liste].