Référence vs copie en Javascript

Référence vs copie en Javascript

16 mars 2022 3 Par Aschen

Dans la programmation, il existe deux moyens de passer des variables à une fonction:

  • par copie: on réalise une copie bit à bit de la variable
  • par référence: on passe l’adresse de la variable

Cela est plus facile à comprendre pour ceux qui sont familiers avec des langages bas niveaux comme C++ mais dans cet article nous allons tenter de comprendre comment cela fonctionne en Javascript ainsi que les erreurs à éviter.

Variables primitives et objets

On va dire pour simplifier qu’il existe deux grandes familles de variables lorsqu’on veut passer des paramètres à une fonction:

  • primitives (e.g. chaines de caractères, entiers, booléens)
  • objets (e.g. POJO, dates, arrays)

Les variables primitives seront passées par copie, c’est à dire que le moteur Javascript réaliser une copie locale de ma variable pour la fonction:

const age = 28;

function add100 (number) {
  number = number + 100;
  // number is 128
}

add100(age);
// age is still 28

Les chaines de caractères ne sont pas exactement des primitives car elles ne sont pas copiées mais elle se comportent de la même manière:

// myName contains a reference to the string "aschen"
const myName = 'aschen';

function toUpperCase (name) {
  // name contains the reference to the string "aschen"

  // name contains a reference to a newly created string "ASCHEN"
  name = name.toUpperCase();
}

toUpperCase(name);
// myName still contains the reference to "aschen"

La conclusion est que lors du passage d’une variable primitive à une fonction, la fonction ne peut pas modifier la valeur original de la variable.

A l’instar des primitives, les objets eux sont passés par référence et peuvent être modifiés par la fonction:

// person contains a reference to an object containing a "name" property
const person = { name: 'aschen '};

function toUpperCase (person) {
  person.name = person.name.toUpperCase();
}

toUpperCase(person);

// the "name" property contained in the object is now "ASCHEN"

En règle général, on évite ce genre de fonction modifiant les propriétés d’un objet car cela produit un code difficilement maintenable et sujet aux effets de bords.

Cloner les objets

Les objets fonctionnent de la même manière que les fonctions à travers leurs propriétés qui peuvent être des primitives ou d’autres objets.

Une erreur courante en Javascript consiste à utiliser le spread operator pour cloner des POJO (Plain Old Javascript Object). Cela fonctionnera très bien si chaque propriété de l’objet cloné est une primitive mais pas si une d’entre elle est un objet!

// each property is a primitive
const aschen = {
  name: 'Aschen',
  age: 28, 
};

const aschenClone = { ...aschen };

aschenClone.name = 'Aschen Bis';

// aschen.name is still "Aschen"
// property "cities" is not a primitive
const aschen = {
  name: 'Aschen',
  cities: ['colombo'],
};

const aschenClone = { ...aschen };

aschenClone.cities.push('pondicherry');

// aschen.cities is ['colombo', 'pondicherry'] because
// aschen.cities and aschenClone.cities are references to the same array

La manière la plus simple de cloner un objet Javascript est d’utiliser JSON.parse et JSON.stringify.

Cela peut paraître overkill mais grâce aux optimisations internes des moteurs Javascript pour le JSON, cela reste très rapide et ne nécessite aucune dépendance.

const aschen = {
  name: 'Aschen',
  cities: ['colombo'],
};

const aschenClone = JSON.parse(JSON.stringify(aschen));

aschenClone.cities.push('pondicherry');

// aschen.cities is ['colombo']

Voir un benchmark des paquets publiés sur NPM pour clôner des POJO: https://gist.github.com/Aschen/bce8195e81f220c6e66b360505e62fc6


Image d’en-tête: Champs de thé à Nuwara Eliya, Sri Lanka