Référence et valeur en JavaScript : ce qui est réellement copié lors d'une affectation

Les valeurs primitives sont copiées, les objets et les tableaux sont partagés par référence. Cette différence explique la moitié des bogues d'affectation.
3 min de lecture
Believemy logo

Affecter une variable à une autre semble toujours faire la même chose. Avec un nombre, la seconde variable devient indépendante. Avec un objet, les deux noms désignent la même chose, et modifier l'une modifie l'autre.

Cette asymétrie n'est pas une bizarrerie de JavaScript : la plupart des langages fonctionnent ainsi. Encore faut-il savoir de quel côté se trouve chaque type.


Définition

Trois lignes qui ressemblent à une copie, et celle qui protège vraiment l'original
1. La ligne de copieconst copie =
2. Ce que vous écrivez ensuite dans la copie
L'original
La copie
Un nombre : deux valeurs, aucun objetlet a = 10; let b = a; b += 5;
a
10
b
15
L'objet profil
profil
nom: "Ada"
adresse: ↓
copie
nom: "Grace"
adresse: ↓
L'objet adresse, un niveau plus bas
profil.adressecopie.adressepartagé
ville: "Londres"
Objets en mémoire : 3Objets partagés par les deux noms : 1
a10intact
profil.nom"Ada"intact

Le premier niveau est bien détaché : profil.nom vaut toujours "Ada". Changez maintenant la cible pour copie.adresse.ville : c'est là que la protection s'arrête.

Le tableau représente la mémoire de façon simplifiée : sur les deux dernières lignes, chaque cadre plein est un objet, et un cadre à cheval sur les deux colonnes est un objet unique atteint par les deux noms. Les cadres en pointillés du haut sont des valeurs, pas des objets, et ne comptent pas. { ...profil } ne recopie que les propriétés propres et énumérables du premier niveau, et transforme les accesseurs (getters) en valeurs figées. structuredClone() a lui aussi ses limites : il refuse une fonction et rend l'objet sans son prototype, donc sans ses méthodes de classe. Le nombre, lui, n'a pas de mode de copie : une primitive est recopiée à chaque affectation, il n'y a aucune adresse à partager.

Modifiez la copie et regardez lequel des deux originaux a bougé. Le troisième cas, celui de l'objet imbriqué, est celui qui surprend le plus.

Une valeur primitive est copiée à chaque affectation : la nouvelle variable reçoit son propre exemplaire. Un objet ne l'est pas : la variable ne contient qu'une référence, une adresse, et l'affecter ailleurs ne fait que dupliquer cette adresse.

JAVASCRIPT
let a = 10;
let b = a;
b += 5;
console.log(a, b); // 10 15

const objetA = { total: 10 };
const objetB = objetA;
objetB.total += 5;
console.log(objetA.total, objetB.total); // 15 15

Dans le second cas, aucun objet n'a été créé à la ligne d'affectation. Il n'y a qu'un objet et deux noms qui pointent dessus.


Ce que ça change pour l'égalité

Deux objets au contenu identique ne sont jamais égaux : la comparaison porte sur l'adresse, pas sur le contenu.

JAVASCRIPT
console.log({ x: 1 } === { x: 1 }); // false
console.log([1, 2] === [1, 2]);     // false

const partage = { x: 1 };
console.log(partage === partage);   // true

C'est vrai pour l'Égalité stricte (===) comme pour l'Égalité faible (==). Pour comparer des contenus, il faut soit comparer champ par champ, soit passer par une sérialisation avec JSON, en gardant en tête que l'ordre des clés compte alors.


Copier pour de vrai

JAVASCRIPT
const profil = { nom: "Ada", adresse: { ville: "Londres" } };

const superficielle = { ...profil };
superficielle.adresse.ville = "Paris";
console.log(profil.adresse.ville); // Paris, l'imbriqué est partagé

const profonde = structuredClone(profil);
profonde.adresse.ville = "Lyon";
console.log(profil.adresse.ville); // Paris, l'original est protégé
Bon à savoir

Le Spread (...) ne copie qu'un niveau. Pour une structure imbriquée, structuredClone() produit une copie complète, y compris des dates et des Map, là où un passage par JSON les aurait perdues.


Questions fréquentes

Question

Un objet passé en argument est-il modifiable par la fonction ?

Oui, la fonction reçoit la même référence et peut modifier le contenu. En revanche, réaffecter le paramètre à l'intérieur ne change rien pour l'appelant : seule la copie locale de l'adresse est remplacée. C'est la nuance qui distingue modifier un objet de le remplacer.


Question

Pourquoi deux dates identiques ne sont-elles pas égales ?

Parce qu'une Date est un objet, donc comparée par référence. Deux instances construites sur le même instant restent deux objets distincts. La comparaison fiable passe par leur valeur numérique, avec a.getTime() === b.getTime().


Question

Les chaînes sont-elles vraiment copiées à chaque fois ?

Du point de vue du langage, oui : une chaîne se comporte comme une valeur et ne peut pas muter. En interne, les moteurs partagent la mémoire tant que personne ne modifie rien, mais cette optimisation est invisible et ne change aucun comportement observable.

Termes connexes

Découvrez notre glossaire JavaScript

Tous les mots de JavaScript expliqués simplement : mots-clés, objets natifs, méthodes, erreurs et concepts. Définitions claires et exemples qui tournent, pour apprendre et pour se dépanner.

Partager cet article

Tu veux nous aider ? Fais un lien vers cet article sur tes réseaux ou encore mieux : sur ton site, dans un article ou dans ta newsletter.