Valeurs primitives : la copie par valeur
Comprendre comment les valeurs primitives sont copiées.
Objectifs
À la fin de cette leçon, vous saurez :
- classer les données JavaScript en deux familles : primitives et objets ;
- expliquer mécaniquement pourquoi modifier une copie d'une primitive ne change jamais l'original ;
- prédire le résultat de toute séquence de copies et réaffectations de primitives ;
- expliquer l'immuabilité des chaînes de caractères.
🔗 Pour vous rafraîchir la mémoire : variables et types primitifs
L'observation de départ
let a = 10;
let b = a; // copie
b = 20; // on modifie la copie
console.log(a); // 10 : l'original n'a pas bougé
console.log(b); // 20
Modifier b n'a aucun effet sur a. Ce n'est ni un hasard ni un détail cosmétique : c'est le comportement fondamental des valeurs primitives, et il découle directement de la façon dont elles sont stockées en mémoire.
Deux familles de valeurs
JavaScript range toutes les données dans deux familles aux comportements opposés :
| Famille | Types | Comportement à la copie |
|---|---|---|
| Primitives | number, string, boolean, null, undefined, bigint, symbol | la valeur est dupliquée |
| Objets | object (dont array), function | l'adresse (référence) est partagée |
Cette leçon traite des primitives ; la suivante traitera des objets, où tout s'inverse. Savoir à quelle famille appartient une donnée est donc la première question à se poser face à un comportement surprenant — typeof y répond :
typeof 42 // "number" → primitive
typeof "texte" // "string" → primitive
typeof true // "boolean" → primitive
typeof {} // "object" → objet
typeof [] // "object" → objet aussi !
Le mécanisme : chaque variable possède son propre exemplaire
Pour les primitives, une variable contient la valeur elle-même, pas un pointeur vers elle. L'affectation let b = a lit la valeur de a et écrit une photocopie dans b. Suivons la mémoire case par case :
Étape 1 : let a = 10;
┌─────────────┐
│ a → 10 │ un seul emplacement existe
└─────────────┘
Étape 2 : let b = a;
┌─────────────┐
│ a → 10 │ la valeur est COPIÉE
│ b → 10 │ deux emplacements indépendants,
└─────────────┘ même contenu pour l'instant
Étape 3 : b = 20;
┌─────────────┐
│ a → 10 │ seul l'emplacement de b change.
│ b → 20 │ Aucun lien ne subsiste entre les cases.
└─────────────┘
L'image mentale utile : deux feuilles de papier photocopiées. Rayer l'une ne raye pas l'autre — elles ont eu le même contenu un instant, puis chacune vit sa vie.
Cela explique aussi pourquoi la modification doit passer par une réaffectation (b = 20) : on remplace le contenu de la case de b. Il n'existe aucun moyen de « toucher » la case de a depuis b — c'est précisément ce qui rend les primitives si sûres.
Vérification complète au REPL
Entraînez-vous à prédire avant chaque Entrée :
> let x = 5
undefined
> let y = x
undefined
> y = y * 100
500
> x
5 ← intact : copie indépendante
> const titre = "cours"
undefined
> titre.length
5
> let autre = titre
undefined
> autre = autre.toUpperCase()
'COURS'
> titre
'cours' ← intact également
Le cas des chaînes mérite son explication dédiée — le voici.
Les chaînes sont immuables
Observez bien le REPL ci-dessus : "cours".toUpperCase() n'a pas transformé la chaîne originale ; il a fabriqué une nouvelle chaîne, que nous avons rangée dans autre. Ce comportement est général :
const salut = "bonjour";
salut.toUpperCase(); // renvoie "BONJOUR", mais salut est intacte
salut.replace("jour", "soir"); // idem : nouvelle chaîne
salut[0] = "B"; // silencieusement sans effet... ou erreur selon le mode
console.log(salut); // "bonjour"
Une chaîne JavaScript est immuable : une fois créée, elle ne change jamais. Toute méthode qui semble « modifier » fabrique en réalité une nouvelle chaîne. Pourquoi cette conception ?
- Sécurité du partage : puisque les chaînes sont copiées partout (par valeur), si l'une pouvait être modifiée, tous ses exemplaires changeraient mystérieusement. L'immutabilité supprime ce risque : une chaîne est figée à jamais ;
- Simplicité mentale :
"abc"signifie toujours, éternellement,"abc"— où qu'il soit passé dans le programme.
Conséquence pratique quotidienne : pour « modifier » un texte, il faut récupérer le résultat :
let nom = "paul";
nom = nom.toUpperCase(); // on REMPLACE la variable par la nouvelle chaîne
console.log(nom); // "PAUL"
Sans l'affectation (nom.toUpperCase(); seule), rien n'aurait changé : la nouvelle chaîne aurait été créée puis aussitôt perdue. Erreur de débutant ultra-fréquente, désormais vous savez pourquoi.
Et typeof null ?
Souvenez-vous du piège célèbre vu au chapitre 3 :
typeof null // "object" ← historique, jamais corrigé
null EST une primitive (copiée par valeur comme les autres) ; la réponse "object" est un bug de la première implémentation de 1995, maintenu pour ne pas casser des millions de sites. Testez-le une fois, retenez-le à vie : c'est exactement le genre d'irrégularité qui distingue celui qui a lu le langage de celui qui devine.
Exercice
Prédisez chaque résultat sans exécuter, notez vos prédictions, puis vérifiez avec Node. C'est l'écart entre prédiction et réalité qui enseigne quelque chose.
let x = 5;
let y = x;
y = y + 1;
console.log(x);
console.log(y);
const titre = "cours";
let autre = titre;
autre += " complet";
console.log(titre);
console.log(autre);
let compteur = 0;
let copieCompteur = compteur;
compteur = 100;
console.log(copieCompteur);
-
Que vaut
typeof null, et pourquoi cette réponse est-elle considérée comme un bug ? -
Expliquez avec vos mots la différence entre modifier une valeur et réaffecter une variable. Sur une primitive, laquelle des deux opérations est possible ?
Résumé
- Deux familles : primitives (copiées par valeur) et objets (partagés par référence).
- Copier une primitive crée un emplacement indépendant : plus aucun lien.
- Modifier une copie n'affecte jamais l'original ; seule la réaffectation touche la variable locale.
- Les chaînes sont immuables : toute transformation produit une nouvelle chaîne, à récupérer explicitement.
typeof nullrépond"object": bug historique à connaître.
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Extrait 1. Affiche 5 puis 6. y a reçu une photocopie de la valeur de x ; y = y + 1 remplace le contenu de la case de y uniquement. La case de x n'existe pour personne d'autre.
Extrait 2. Affiche cours puis cours complet. autre += " complet" fabrique une troisième chaîne (concaténation) et la range dans autre. La chaîne originale référencée par titre reste immuable et intacte.
Extrait 3. Affiche 0. Point crucial : la copie a eu lieu au moment de l'affectation. copieCompteur a reçu la valeur du moment (0), pas un lien vivant vers la variable compteur. Une copie par valeur est une photo instantanée, jamais une surveillance en direct.
Question 4. "object". Bug de la première version du langage (le code de typeof confondait null avec les objets) ; corriger signifierait briser des sites existants, donc le standard l'a officialisé tel quel. Astuce de vérification : null === null vaut true et il n'y a qu'un seul null — comportement de primitive.
Question 5. Réponse attendue (formulations libres acceptées) : modifier change le contenu d'une valeur déjà existante — possible seulement sur des objets (user.age = 21). Réaffecter fait pointer une variable vers une nouvelle valeur, sans toucher l'ancienne (b = 20) — possible sur toute variable non const. Sur une primitive immuable (chaîne), seule la réaffectation existe : impossible de modifier la valeur elle-même. Cette distinction devient centrale dès la prochaine leçon, où les objets permettront enfin la vraie modification — avec les surprises qui vont avec.