En JavaScript, comme dans d’autres langages de programmation nous sommes souvent amenés lors de nos développement à manipuler des données de tous types. Cependant, en JavaScript on a cette notion de passage/copie par valeur ou par référence, ce qui peut rendre les choses un peu confuses lorsqu’il s’agit de manipuler des tableaux ou des objets. Nous allons essayer de comprendre cette notion-là en regardant de plus près les différents types de données, qu’est ce qu’une copie par référence et par valeur.

Les types de données

En JavaScript, il existe 7 types de données.

  • Number
  • String
  • Boolean
  • Symbol
  • Object
  • undefined
  • null

Il faut savoir que parmi les types de données précédemment cités, on retrouve des types de données primitifs ou simples et des non-primitifs, complexes ou objets. Voyons un peu plus ce que c’est et quelle est la différence entre ces deux types ?

Types de données primitifs

Une primitive est une donnée qui n’est pas un objet et n’a pas de méthode. En JavaScript, les types de données primitifs sont : Number, String, Boolean, Symbol, undefined et null.

Une valeur primitive est représentée directement au plus bas niveau dans l’implémentation du langage, sa valeur en mémoire est sa valeur réelle. Ces types de données sont également immuables, ce qui signifie qu’une fois créés, ils ne peuvent être modifiés.

On dit que ces types de données sont passés, copiés par valeur.

// Number
const number1 = 1;
const number2 = Number.parseInt("1.2"); // ou Number.parseFloat("1.2");
const number3 = Number(123);
const number4 = +"123"; // ou "123"*1

// String
const string1 = "toto";
const string2 = 'toto';
const string3 = `toto`;
const string4 = 1 + "toto"

// Boolean
const boolean1 = !true;
const boolean2 = true && false;
const boolean3 = false || true;
const boolean4 = 123 === "123";

// Symbol
const symbol = Symbol("xyz");

// undefined
const undefinedVar = undefined; // ou undefined tout simplement sans l'assigner

// null
const nullVar = null;

Toutefois typeof null renvoie un type object. Aussi la comparaison entre null et undefined avec l’égalité stricte (ou triple égal => type et valeur) utilisant === n’est pas la même que l’égalité faible (ou double égal => valeur) utilisant ==

typeof null // "object"

null == undefined; // true
undefined == null; // true
undefined === null; // false

Types de données non-primitifs

Un objet est une valeur conservée en mémoire à laquelle on fait référence grâce à un identifiant. En JavaScript, les types de données non-primitifs ou objets sont : Object, Array, Function. On dit que ces types de données sont passés, copiés par référence.

// Object
const object = {
    id: 1,
    title: "My todo",
    description: "Do something",
    status: "pending"
}

// Array
const array = ["foo", "bar", "toto"];

// Function
const sayHello = function() {
    console.log("Hello!");
}

Ici typeof array renvoie un type object

Passage ou copie par valeur

Un passage ou une copie par valeur signifie une copie de la valeur originale. La copie n’a aucune référence vers l’originale. Toute modification sur la valeur copiée n’aura aucun impact sur la valeur originale.

let status = "pending";

let newStatus = status;

console.log(newStatus);
//=> "pending"

newStatus = "done";

console.log(newStatus);
//=> "done"

console.log(status);
//=> "pending"

let a = 1;
let b = "todo";

function myFunction(a, b) {
  a = a + 2;
  b = "my " + b;
  
  console.log(a, b);
}

myFunction(a, b);
//=> 3 "my todo"

console.log(a, b);
//=> 1 "todo"

Passage ou copie par référence

Un passage ou une copie par référence signifie créer une référence à l’originale. Les deux seront forcément liées. Toute modification qui se produit sur la copie se répercutera également sur l’originale.

const todo = {
    id: 1,
    title: "My first todo",
    description: "Do something",
    status: "pending"
}

// On crée une copie de todo
const copyOfTodo = todo;

// On met à jour la proprité isDone de copyOfTodo
copyOfTodo.status = "done";

console.log(copyOfTodo)
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/
console.log(todo)
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

const a = {
    id: 1,
    title: "My first todo",
    description: "Do something",
    status: "pending"
}

function myFunction(a) {
  const b = a;

  b.status = "done";
  
  console.log(a, b);
}

myFunction(a, b);
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

console.log(a);
/*
{
  "id": 1,
  "title": "My first todo",
  "description": "Do something",
  "status": "done"
}
*/

Quelques erreurs à éviter

Deux objets contenant les mêmes clés/valeus ne seront pas strictement égaux, puisque cette égalité implique la référence de chaque objet qui n’est pas la même. Pour que deux objets soient strictement égaux, il est nécessaire qu’ils aient une référence au même objet.

On a aussi la possibilité de les comparer après avoir effectué un deepClone (copie réelle de l’objet, les deux objets ne dépendent pas l’un de l’autre), un shallowClone (les deux objets sont attachés au même bloc mémoire), ouJSON.parse(JSON.stringify(original)) qui peut être utilisée comme dernier recours mais n’est pas forcément la bonne solution. J’utilise le plus souvent deepClone pour être certain à coup sûr.

Aussi, les tableaux, les dates et les fonctions ne seront pas strictement égaux.

On verra dans un prochain article les différentes méthodes pour cloner un objet, les cas d’utilisation ainsi que comment comparer certains types de données.

En résumé

JavaScript effectue toujours un passage/copie par valeur lorsqu’il s’agit de types de données primitifs et un passage/copie par référence lorsqu’il s’agit de types de données non-primitifs.

Il faut éviter le plus possible quand ce n’est pas nécessaire de modifier les objets passés par référence (copie ou passage à une fonction) afin d’éviter les effets de bords ce que la programmation fonctionnelle met en avant (pure functions, immutability…) qu’on abordera dans d’autres articles également.

Avant de partir…
Merci pour votre lecture ! 😊