Aller au contenu

Javascript • Système de types • Collections

Dans cette série d’articles, nous faisons un focus sur les fonctionnalités de base de Javascript.

Cet article concerne le système de types proposé par Javascript. Nous allons détailler maintenant les types de collection.

Javascript possède un typage dynamique. Qu’est-ce-que cela signifie exactement ?

🔑 Nous ne déclarons pas le type d’une variable avant de l’utiliser.

🔑 Le type d’une variable est déterminé à l’exécution (et pas à la compilation).

🔑 Le type d’une variable peut changer pendant sa durée de vie.

// toto est une chaine de caractères (string)
let toto = "text";
// toto est maintenant un nombre (number)
toto = 69;
// toto est maintenant un booléen (boolean)
toto = true;

Javascript est également faiblement typé. Il permet ainsi des conversions implicites de type.

const oneTwoThree = 123; // number
const fourFive = "45"; // string
const result = oneTwoThree + fourFive; // string
console.log(result); // ← "12345"

Le typage Javascript est divisé en deux mondes : les types de valeurs primitives (ou types primitifs) et les types objet. Les types Objet regroupent :

Les types objet sont des types référence (reference type) : une variable d’un type object contient la référence à la valeur. Nous pouvons aussi dire qu’elle pointe vers cette valeur.

Les variables d’un type objet sont muables (mutable ) : nous pouvons changer la valeur d’un objet après sa création, contrairement aux valeurs primitives.

Nous allons maintenant nous concentrer dans cet article sur les collections

Le type array est un type d’objet qui permet de stocker une collection indexée. Les éléments de cette collection peuvent être de n’importe quel type. La collection n’est pas forcément homogène : tous les éléments ne sont pas forcément du même type.

Chaque élément est associé à un index. L’index est un nombre entier positif. Le premier index vaut 0 (et pas 1).

const mixedArray = [
"ABC", // type = string, index = 0
(x, y) => x + y, // type = function, index = 1
null, // type = null, index = 2
-123, // type = number, index = 3
undefined, // type = undefined, index = 4
{ name: "Bob" }, // type = object, index = 5
[ 7, "X" ], // type = array, index = 6
{ height: 16, width: 32 } // type = object, index = 7
];

L’object wrapper associé à array est : Array. Il propose de très nombreuses méthodes dont certaines sont présentées dans cet article.

Comme nous venons de le voir dans les deux exemples précédents, nous pouvons créer une variable contenant un tableau en utilisant la syntaxe []. Le tableau sera créé et rempli avec les valeurs entre [ et ].

const emptyArray = [];
const fibonacciSequence = [ 0, 1, 2, 3, 5, 8, 13, 21 ];
const primaryColors = [ "blue", "green", "red" ];

Nous pouvons également utiliser le constructeur de tableau new Array().

const emptyArray = new Array();
const fibonacciSequence = new Array(0, 1, 2, 3, 5, 8, 13, 21);
const primaryColors = new Array("blue", "green", "red");

Une variable de type array possède la propriété length. La valeur de length est toujours égale à la valeur de l’index le plus élevé + 1. length ne représente donc pas forcément le nombre d’éléments du tableau.

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
console.log(mixedArray.length); // ← 8

Nous pouvons accéder à un élément d’un tableau via l’indexeur []. La valeur entre [ et ] correspond à l’index de l’élément que nous souhaitons récupérer.

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
console.log(mixedArray[0]); // ← "ABC"
console.log(mixedArray[1]); // ← function 1(x, y)
console.log(mixedArray[2]); // ← null
console.log(mixedArray[3]); // ← -123
console.log(mixedArray[4]); // ← undefined
console.log(mixedArray[5]); // ← Object { name: "Bob" }
console.log(mixedArray[6]); // ← Array [ 7, "X" ]
console.log(mixedArray[7]); // ← Object { height: 16, width: 32 }
console.log(mixedArray[8]); // ← undefined
console.log(mixedArray[9]); // ← undefined
console.log(mixedArray[10]); // ← undefined
console.log(mixedArray.length); // ← 8

Si l’index n’existe pas alors undefined est retourné.

Nous pouvons créer ou modifier un élement en utilisant l’indexeur []. La valeur entre [ et ] correspond à l’index de l’élément que nous souhaitons créer ou modifier.

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
mixedArray[0] = (a, b) => a - b;
mixedArray[1] = "DEF";
mixedArray[2] = 3.1415;
mixedArray[10] = [ 10, 9, 8 ];
mixedArray["prop"] = "val";
console.log(mixedArray[0]); // ← function 0(a, b)
console.log(mixedArray[1]); // ← "DEF"
console.log(mixedArray[2]); // ← 3.1415
console.log(mixedArray[3]); // ← -123
console.log(mixedArray[4]); // ← undefined
console.log(mixedArray[5]); // ← Object { name: "Bob" }
console.log(mixedArray[6]); // ← Array [ 7, "X" ]
console.log(mixedArray[7]); // ← Object { height: 16, width: 32 }
console.log(mixedArray[8]); // ← undefined
console.log(mixedArray[9]); // ← undefined
console.log(mixedArray[10]); // ← Array(3) [ 10, 9, 8 ]
console.log(mixedArray.length); // ← 11
console.log(mixedArray.prop); // ← "val"

Nous pouvons utiliser l’instruction for…of pour obtenir toutes les valeurs d’un tableau.

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
// ← "ABC"
// ← function mixedArray(x, y)
// ← null
// ← -123
// ← undefined
// ← Object { name: "Bob" }
// ← Array [ 7, "X" ]
// ← Object { height: 16, width: 32 }
for (const element of mixedArray) {
console.log(element);
}

Nous pouvons également utiliser la méthode d’instance Array.prototype.forEach().

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
// ← [0] : "ABC"
// ← [1] : function mixedArray(x, y)
// ← [2] : null
// ← [3] : -123
// ← [4] : undefined
// ← [5] : Object { name: "Bob" }
// ← [6] : Array [ 7, "X" ]
// ← [7] : Object { height: 16, width: 32 }
mixedArray.forEach((element, index) =>
console.log(`[${index}] :`, element)
);

Finalement, nous pouvons utiliser l’instruction for.

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
// ← [0] : "ABC"
// ← [1] : function mixedArray(x, y)
// ← [2] : null
// ← [3] : -123
// ← [4] : undefined
// ← [5] : Object { name: "Bob" }
// ← [6] : Array [ 7, "X" ]
// ← [7] : Object { height: 16, width: 32 }
for (let index = 0; index < mixedArray.length; index++) {
console.log(`[${index}] :`, mixedArray[index])
}

Comme pour le type objet, nous pouvons effectuer une copie superficielle grace au spread operator (…).

const mixedArray = [
"ABC",
(x, y) => x + y,
null,
-123,
undefined,
{ name: "Bob" },
[ 7, "X" ],
{ height: 16, width: 32 }
];
const copyOfMixedArray = [ "before", ...mixedArray, "after" ];
// ← [0] : "before"
// ← [1] : "ABC"
// ← [2] : function mixedArray(x, y)
// ← [3] : null
// ← [4] : -123
// ← [5] : undefined
// ← [6] : Object { name: "Bob" }
// ← [7] : Array [ 7, "X" ]
// ← [8] : Object { height: 16, width: 32 }
// ← [9] : "after"
copyOfMixedArray.forEach((element, index) =>
console.log(`[${index}] :`, element)
);
console.log(copyOfMixedArray.length); // ← 10

Le tableau copyOfMixedArray contient :

  1. l’élément “before” (index 0)
  2. la liste des éléments du tableau mixedArray via ...mixedArray (index 1 à 8) ;
  3. l’élément “after” (index 9)

Les tableaux peuvent être imbriqués : chaque élément d’un tableau peut lui-même être un autre tableau. Nous pouvons ainsi créer un tableau multi-dimensionnel.

const multidimensionalArray = new Array(3); // Nouveau tableau de 3 éléments
for (let i = 0; i < 3; i++) {
multidimensionalArray[i] = new Array(3);
for (let j = 0; j < 3; j++) {
multidimensionalArray[i][j] = `[${i}${j}]`;
}
}
// ← Array(3) [ (3) […], (3) […], (3) […] ]
// 0: Array(3) [ "[0 → 0]", "[0 → 1]", "[0 → 2]" ]
// 1: Array(3) [ "[1 → 0]", "[1 → 1]", "[1 → 2]" ]
// 2: Array(3) [ "[2 → 0]", "[2 → 1]", "[2 → 2]" ]
// length: 3
console.log(multidimensionalArray);

Le type Map permet de stocker une collection de paires de clef-valeur (key-value pairs). Il fait partie des collections avec clef.

Le type object permet lui aussi de stocker une collection de clef-valeur. Il y a cependant des différences.

CaractéristiqueMapObject
Clefs accidentellesMap ne contient aucune clé par défaut. Il ne contient que les éléments que nous avons explicitement ajoutés.Object possède un prototype et dispose donc de certaines clefs par défaut, ce qui peut être source de collision avec nos propres clefs si nous ne sommes pas vigilants.
Types de clefMap accepte n’importe quel type de valeur (primitive ou objet) comme clef.Object accepte uniquement le type string ou symbol comme clef.
Ordre des élémentsMap respecte l’ordre d’insertion des éléments.Object ne garantie pas l’ordre des éléments. Cet ordre dépends également de l’itération utilisée.
TailleLe nombre d’éléments est accessible avec la propriété size.Le nombre d’éléments d’un Object doit être déterminé manuellement.
ItérationMap est un itérable et peut-être itéré via for…of.Object n’est pas directement itérable. Nous pouvons néanmoins utiliser for…in.
PerformancesMap propose de meilleures performances en cas de nombreuses ajouts/modifications/suppressions d’éléments.Object ne propose pas d’optimisation particulière.
Sérialisation et désérialisationMap ne propose pas de prise en charge native de la sérialisation/désérialisation. Il est néanmoins possible d’y arriver en fournissant une fonction de conversion à JSON.stringify() et JSON.parse().Object est nativement sérialisable.

Le type Map est équivalent au type .NET Dictionary<TKey,TValue>.

Nous pouvons créer une variable de type Map en utilisant le constructeur new Map().

const emptyMap = new Map();
console.log(emptyMap); // ← Map(0)

Le constructeur accepte comme paramètre un itérable dont les éléments sont des clefs-valeurs.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
// ← Map(7) {
// 1 → "lundi",
// 2 → "mardi",
// 3 → "mercredi",
// 4 → "jeudi",
// 5 → "vendredi",
// 6 → "samedi",
// 7 → "dimanche"
// }
console.log(weekDays);

La propriété d’instance Map.prototype.size retourne le nombre d’éléments courant d’une variable de type Map.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
console.log(weekDays.size); // ← 7

La méthode d’instance Map.prototype.get(key) retourne la valeur associée à la clef passée en paramètre de la fonction.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
console.log(weekDays.get(0)); // ← undefined
console.log(weekDays.get(1)); // ← "lundi"
console.log(weekDays.get(2)); // ← "mardi"
console.log(weekDays.get("3")); // ← undefined

Si aucun élément ne correspond à la clef spécifiée alors undefined est retourné.

La méthode d’instance Map.prototype.set(key, value) permet de créer ou modifier la valeur associée à la clef.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[6, "samedi"],
[7, "dimanche"]
]);
weekDays.set(5, "vendredi");
weekDays.set(7, "Dimanche");
// ← Map(7) {
// 1 → "lundi",
// 2 → "mardi",
// 3 → "mercredi",
// 4 → "jeudi",
// 6 → "samedi",
// 7 → "Dimanche"
// 5 → "vendredi",
// }
console.log(weekDays);

La méthode d’instance Map.prototype.delete(key) permet de supprimer l’élément associé à la clef.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[6, "samedi"],
[7, "dimanche"]
]);
weekDays.delete(1); // ← true
weekDays.delete("1"); // ← false
// ← Map(6) {
// 2 → "mardi",
// 3 → "mercredi",
// 4 → "jeudi",
// 6 → "samedi",
// 7 → "Dimanche"
// 5 → "vendredi",
// }
console.log(weekDays);

La méthode delete() retourne true si l’élément a bien été supprimé, sinon elle retourne false.

La méthode d’instance Map.prototype.clear(key) permet de supprimer tous les éléments.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[6, "samedi"],
[7, "dimanche"]
]);
weekDays.clear();
console.log(weekDays); // ← Map(0)

La méthode d’instance Map.prototype.has(key) retourne true si un élément avec la clef spécifiée est présent dans la variable de type Map.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
console.log(weekDays.has(0)); // ← false
console.log(weekDays.has(1)); // ← true
console.log(weekDays.has(2)); // ← true
console.log(weekDays.has("3")); // ← false

Nous pouvons utiliser l’instruction for…of pour obtenir tous les éléments d’une variable de type Map.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
// ← Array [ 1, "lundi" ]
// ← Array [ 2, "mardi" ]
// ← Array [ 3, "mercredi" ]
// ← Array [ 4, "jeudi" ]
// ← Array [ 5, "vendredi" ]
// ← Array [ 6, "samedi" ]
// ← Array [ 7, "dimanche" ]
for (const day of weekDays) {
console.log(day);
}

La méthode d’instance Map.prototype.forEach(callbackFn) permet également de parcourir tous les éléments.

const weekDays = new Map([
[1, "lundi"],
[2, "mardi"],
[3, "mercredi"],
[4, "jeudi"],
[5, "vendredi"],
[6, "samedi"],
[7, "dimanche"]
]);
// ← 1 → "lundi"
// ← 2 → "mardi"
// ← 3 → "mercredi"
// ← 4 → "jeudi"
// ← 5 → "vendredi"
// ← 6 → "samedi"
// ← 7 → "dimanche"
weekDays.forEach((value, key) => {
console.log(`${key}${value}`);
});

Le type Set permet de stocker une collection de valeurs uniques :chaque valeur ne peut apparaitre qu’une seule fois.

Une variable de type Set conserve l’ordre d’insertion.

Afin de faciliter la compréhension, nous pouvons considérer qu’une variable de type Set est l’équivalent d’un variable de type Map avec clef = valeur.

Contrairement à une variable de type array, une variable de type Set n’est pas directement sérialisable. Nous pouvons facilement contourner cette limitation en utilisant la méthode d’instance Set.prototype.entries() qui retourne tous les éléments sous forme de tableau.

Nous pouvons créer une variable de type Map en utilisant le constructeur new Set().

const emptySet = new Set();
console.log(emptySet); // ← Set(0)

Le constructeur accepte comme paramètre un itérable.

const primaryColors = new Set(["red", "green", "blue"]);
console.log(primaryColors); // ← Set(3) [ "red", "green", "blue" ]

La propriété d’instance Set.prototype.size retourne le nombre d’éléments courant d’une variable de type Set.

const primaryColors = new Set(["red", "green", "blue"]);
console.log(primaryColors.size); // ← 3

La méthode d’instance Set.prototype.add(element) permet d’ajouter un nouvel élément.

const primaryColors = new Set(["red"]);
primaryColors.add("green");
primaryColors.add("blue").add("red");
console.log(primaryColors); // ← Set(3) [ "red", "green", "blue" ]

La méthode add() retourne l’instance du Set afin de pouvoir chainer les appels de méthode.

Si un élément existe déjà alors la méthode add() n’a aucun effet.

La méthode d’instance Set.prototype.delete(element) permet de supprimer l’élément.

const primaryColors = new Set(["red", "green", "blue", "black"]);
primaryColors.delete("black"); // ← true
primaryColors.delete("white"); // ← false
console.log(primaryColors); // ← Set(3) [ "red", "green", "blue" ]

La méthode delete() retourne true si l’élément a bien été supprimé, sinon elle retourne false.

La méthode d’instance Set.prototype.clear() permet de supprimer tous les éléments.

const primaryColors = new Set(["red", "green", "blue"]);
primaryColors.clear();
console.log(primaryColors); // ← Set(0)

La méthode d’instance Set.prototype.has(element) retourne true si l’élément est présent dans la variable de type Set.

const primaryColors = new Set(["red", "green", "blue"]);
primaryColors.has("red"); // ← true
primaryColors.has("Red"); // ← false
primaryColors.has("green"); // ← true

Nous pouvons utiliser l’instruction for…of pour obtenir tous les éléments d’une variable de type Set.

const primaryColors = new Set(["red", "green", "blue"]);
// ← "red"
// ← "green"
// ← "blue"
for (const color of primaryColors) {
console.log(color);
}

La méthode d’instance Set.prototype.forEach(callbackFn) permet également de parcourir tous les éléments.

const primaryColors = new Set(["red", "green", "blue"]);
// ← "red"
// ← "green"
// ← "blue"
primaryColors.forEach(color => {
console.log(color);
});

Nous avons vu les principaux types de collection.

Le type array est le type de collection le plus ancien proposé par Javascript. Il dispose de méthodes d’instance puissantes pour effectuer de nombreuses manipulations. Certaines de ces méthodes sont présentées dams cet article.

Les types Map et Set sont plus récents.

Map nous permet de créer un dictionnaire. Dans certains cas, il peut avantageusement remplacer un object qui remplissait historiquement cette fonction.

Set nous permet de créer une collection sans doublon. Cela peut nous être très utile dans certaines situations.

Dans l’article suivant, nous allons détailler le type function proposé par Javascript.