W3docs

Références et copie d'objets JavaScript

Comprenez comment JavaScript stocke et copie les objets par référence, comment cloner avec Object.assign et le spread, et comment effectuer une copie profonde avec structuredClone().

L'une des distinctions les plus importantes en JavaScript est la façon dont il traite les valeurs primitives par rapport aux objets. Les primitives sont copiées « en tant que valeur entière », mais les objets sont stockés et copiés « par référence ». Mal comprendre cette seule règle est à l'origine d'innombrables bugs, c'est pourquoi ce guide explique en détail ce qui se passe en mémoire, comment comparer et cloner des objets, et comment les copier en toute sécurité.

Les primitives sont copiées par valeur

Une primitive — un string, un nombre, un boolean, null, undefined, bigint ou un symbole — est copiée comme une valeur complète et indépendante. Assigner une variable à une autre duplique la valeur, de sorte que les deux variables sont entièrement séparées ensuite.

let message = "Hello";
let phrase = message; // a full copy of the string is made

phrase = "Goodbye";

console.log(message); // "Hello"  — unaffected
console.log(phrase);  // "Goodbye"

Modifier phrase n'a aucun effet sur message. Il y a deux strings indépendants en mémoire.

Les objets sont stockés et copiés par référence

Les objets fonctionnent différemment. Une variable assignée à un object ne contient pas l'object lui-même — elle contient une référence (un pointeur) vers l'endroit où l'object réside en mémoire. Copier cette variable copie la référence, pas l'object. Les deux variables pointent alors vers le même object.

javascript— editable

Il n'y a toujours qu'un seul object. Nous avons simplement deux variables — user et admin — qui y font toutes les deux référence. Une modification effectuée via l'une est visible via l'autre, car elles décrivent la même chose.

Comparaison par référence

Deux variables d'object sont égales avec == ou === uniquement lorsqu'elles référencent le même object. Deux objets indépendants ne sont jamais égaux, même lorsque leurs contenus semblent identiques.

javascript— editable

C'est intentionnel : === pour les objets demande « est-ce le même object ? », et non « ces objets ont-ils les mêmes données ? ». Comparer les contenus nécessite une approche différente (souvent en comparant les propriétés une à une, ou en sérialisant avec JSON).

Les objets const peuvent toujours être mutés

Une surprise fréquente : un object déclaré avec const peut toujours avoir ses propriétés modifiées. const fige la liaison — la variable ne peut jamais être réassignée à une valeur différente — mais il ne fige pas le contenu de l'object.

const user = { name: 'John' };

user.name = 'Pete'; // OK — we are mutating the object, not reassigning the variable
console.log(user.name); // 'Pete'

// user = { name: 'Alice' }; // TypeError: Assignment to constant variable

La première modification fonctionne car user référence toujours le même object. La réassignation échoue car elle ferait pointer user vers un tout nouvel object, ce que const interdit. (Pour figer également le contenu, utilisez Object.freeze().)

Clonage superficiel

Et si vous voulez vraiment une copie séparée d'un object ? Vous devez créer un nouvel object et y copier les propriétés existantes. Deux façons modernes et concises de faire cela sont Object.assign et la syntaxe spread.

Object.assign(target, ...sources) copie toutes les propriétés propres énumérables des sources vers la cible et retourne la cible :

javascript— editable

La syntaxe spread {...obj} fait la même chose sous une forme encore plus courte (voir paramètres rest et syntaxe spread) :

let user = { name: 'John', age: 30 };

let clone = { ...user };       // a shallow copy
let merged = { ...user, age: 31 }; // copy, then override age

console.log(clone);  // { name: 'John', age: 30 }
console.log(merged); // { name: 'John', age: 31 }

Les deux techniques produisent une copie réelle et indépendante — mais uniquement au premier niveau. Ce qualificatif est important, et il mène à la section suivante.

Le problème des objets imbriqués

Une copie superficielle duplique les propriétés de premier niveau. Mais si la valeur d'une propriété est elle-même un object ou un array, seule la référence vers cet object imbriqué est copiée — pas l'object imbriqué lui-même. Le clone et l'original partagent donc le même object imbriqué.

javascript— editable

Même si clone est un object de premier niveau distinct, clone.sizes et user.sizes pointent vers un seul et même object imbriqué. Le muter par l'un ou l'autre chemin affecte les deux. C'est exactement le type de bug de référence partagée accidentelle qui piège les personnes supposant qu'une copie spread est « entièrement indépendante ».

Avertissement

Spread ({...obj}) et Object.assign ne copient qu'un seul niveau en profondeur. Si votre object contient des objets ou arrays imbriqués, la copie partage ces valeurs imbriquées avec l'original — muter une propriété imbriquée via l'un changera silencieusement l'autre. Pour des données imbriquées, utilisez une copie profonde.

Clonage profond avec structuredClone()

Pour copier un object et tout ce qu'il contient en profondeur, utilisez le structuredClone() intégré. Il clone récursivement les objets et arrays imbriqués, de sorte que le résultat est entièrement indépendant de l'original.

javascript— editable

structuredClone gère également les références circulaires (un object qui, directement ou indirectement, se référence lui-même) sans planter :

let user = {};
user.self = user; // user references itself

let clone = structuredClone(user);

console.log(clone === clone.self); // true — the cycle is preserved correctly

Au-delà des objets et arrays simples, il prend en charge de nombreux types intégrés, notamment Date, Map, Set, RegExp, ArrayBuffer et les tableaux typés — copiant leurs valeurs fidèlement plutôt que de les transformer en autre chose.

Limitations de structuredClone

structuredClone est puissant mais pas universel :

  • Il ne peut pas cloner les fonctions — tenter de le faire lance une DataCloneError. Il en va de même pour les nœuds DOM.
  • Il ne copie pas les propriétés à clé symbole, les getters/setters de propriétés, ni la chaîne prototype de l'object — ceux-ci sont simplement supprimés du résultat.
// This throws DataCloneError because functions can't be cloned:
// structuredClone({ run: () => {} });

Si vous avez besoin de copier des fonctions ou des instances de classe avec leurs méthodes, structuredClone n'est pas l'outil adapté — vous devrez écrire un clone personnalisé, ou utiliser une bibliothèque comme cloneDeep de lodash.

L'ancienne astuce JSON

Avant que structuredClone soit largement disponible, une astuce populaire de clonage profond consistait à sérialiser un object en string JSON et à le réanalyser :

let user = { name: 'John', sizes: { height: 182, width: 50 } };

let clone = JSON.parse(JSON.stringify(user)); // deep copy via JSON

clone.sizes.width = 60;
console.log(user.sizes.width); // 50 — original unaffected

Cela fonctionne pour des données simples et compatibles JSON, mais présente de vrais inconvénients :

  • Les fonctions et undefined sont supprimées — les clés qui les contiennent disparaissent simplement.
  • Les objets Date deviennent des stringsJSON.stringify transforme une date en string ISO, et l'analyse ne la reconvertit pas.
  • Map, Set et autres types spéciaux sont perdus ou deviennent des objets vides.
  • Les références circulaires lancent une TypeError.
Info

Pour le clonage profond, préférez structuredClone() à l'astuce JSON.parse(JSON.stringify(...)). L'approche JSON perd silencieusement les fonctions, undefined et les types spéciaux, altère les dates et plante sur les références circulaires — structuredClone gère tout cela correctement.

Une note sur le ramasse-miettes

Une fois que vous commencez à copier des références, il est utile de se rappeler comment JavaScript récupère la mémoire. Un object reste en mémoire tant qu'il est accessible — tant qu'une variable, une propriété ou une entrée d'array y fait encore référence. Lorsque la dernière référence à un object disparaît, il devient inaccessible et devient éligible au ramasse-miettes. Vous ne libérez jamais les objets manuellement ; le moteur le fait automatiquement.

En pratique : copier l'état avant une mutation

Ce n'est pas théorique. Dans le code UI moderne (React, Redux et similaires), la règle standard est « ne jamais muter l'état directement — produire une nouvelle copie avec la modification appliquée ». La copie superficielle avec spread est l'outil quotidien pour cela :

javascript— editable

Notez que nous utilisons spread à la fois sur l'object de premier niveau et sur l'array imbriqué cart. Comme le spread est superficiel, vous devez explicitement copier chaque niveau imbriqué que vous souhaitez modifier — sinon vous retombez directement dans le piège de référence partagée décrit précédemment. Lorsque les données sont profondément imbriquées et que vous avez besoin d'une copie complète et sûre, utilisez structuredClone.

Résumé

  • Les primitives sont copiées par valeur — les copies sont entièrement indépendantes.
  • Les objets sont stockés et copiés par référence — copier la variable copie le pointeur, donc les deux variables partagent un seul object.
  • === compare les objets par référence : seul le même object est égal à lui-même.
  • const fixe la liaison, pas le contenu — les objets const peuvent toujours être mutés.
  • Object.assign({}, obj) et {...obj} font des copies superficielles qui partagent les objets imbriqués.
  • structuredClone(obj) fait une copie profonde, gère les références circulaires et de nombreux types intégrés, mais ne peut pas cloner les fonctions ou les nœuds DOM et supprime les clés symbole, les getters et les prototypes.
  • Préférez structuredClone à l'astuce avec perte JSON.parse(JSON.stringify(...)).

Testez vos connaissances

Pratique
Après let a = {}; let b = a; b.x = 1; — quelle est la valeur de a.x ?
Après let a = {}; let b = a; b.x = 1; — quelle est la valeur de a.x ?
Pratique
Étant donné let c = { n: 1 }; let d = { n: 1 }; — à quoi c === d s'évalue-t-il ?
Étant donné let c = { n: 1 }; let d = { n: 1 }; — à quoi c === d s'évalue-t-il ?
Pratique
Quelle affirmation concernant la copie d'objets en JavaScript est correcte ?
Quelle affirmation concernant la copie d'objets en JavaScript est correcte ?
Was this page helpful?