Types et structures de données JavaScript
Les langages de programmation disposent de structures de données natives. Selon les langages, les structures mises à disposition peuvent être différentes. Dans cet article, on listera les structures de données natives en JavaScript. On détaillera leurs propriétés et les façons de les utiliser voire de les combiner. Dans certains cas, on comparera ces structures avec celles d'autres langages.
Un typage dynamique
JavaScript est un langage dont le typage est faible et dynamique. Cela signifie qu'il n'est pas nécessaire de déclarer le type d'une variable avant de l'utiliser. Le type de la variable sera automatiquement déterminé lorsque le programme sera exécuté. Cela signifie également que la même variable pourra avoir différents types au cours de son existence :
let toto = 42; // toto est un nombre
toto = "truc"; // toto est désormais une chaîne de caractères
toto = true; // toto est désormais un booléen
Les types de données JavaScript
L'ensemble des types disponible en JavaScript se compose des valeurs primitives et des objets.
-
Les valeurs primitives (des données immuables, représentées au niveau le plus bas du langage)
-
Les objets (des ensembles de propriétés)
Les valeurs primitives
Tous les types, sauf les objets, définissent des valeurs immuables (qu'on ne peut modifier). Ainsi, contrairement au C, les chaînes de caractères sont immuables en JavaScript. Les valeurs immuables pour chacun de ces types sont appelées « valeurs primitives ».
Le type booléen
Un booléen représente le résultat d'une assertion logique et peut avoir deux valeurs : true
(pour le vrai logique) et false
(pour le faux logique) (voir Boolean
pour plus de détails sur la représentation objet de ce type).
Le type nul
Le type nul ne possède qu'une valeur : null
. Voir null
et la page du glossaire pour plus d'informations.
Le type indéfini
Une variable à laquelle on n'a pas affecté de valeur vaudra undefined
. Voir undefined
et la page du glossaire pour plus d'informations.
Les types numériques
Le type nombre
Le type Number
est géré pour représenter les nombres : les nombres flottants à précision double, représentés sur 64 bits, selon le format IEEE 754. Cette représentation permet de stocker des nombres décimaux entre 2^-1074
et 2^1024
, mais ne permet de représenter des entiers de façon sûre qu'au sein de l'intervalle allant de -(2^53 − 1)
à 2^53 − 1
. Les valeurs en dehors de l'intervalle compris entre Number.MIN_VALUE
et Number.MAX_VALUE
sont automatiquement converties en +Infinity
ou -Infinity
, qui se comporteront de façon analogue à l'infini mathématique (voir la page sur Number.POSITIVE_INFINITY
pour les détails et les quelques différences).
Note :
Vous pouvez vérifier si un nombre est un nombre entier représentable de façon exacte avec une représentation en nombre flottant à double précision avec la méthode Number.isSafeInteger()
. En dehors de l'intervalle entre Number.MIN_SAFE_INTEGER
et Number.MAX_SAFE_INTEGER
, JavaScript ne peut plus représenter un entier de façon exacte et ce sera une approximation avec un nombre flottant à double précision.
Pour le type Number
, il n'y a qu'un seul nombre qui possède plusieurs représentations : 0
qui est représenté comme -0
et +0
(avec 0
étant un synonyme pour +0
). En pratique, il n'y a presque pas de différences entre ces représentations et +0 === -0
vaut true
. Toutefois, on pourra remarquer la nuance lors de la division par zéro :
> 42 / +0
Infinity
> 42 / -0
-Infinity
Dans la plupart des cas, un nombre représente sa propre valeur et JavaScript fournit des opérateurs binaires.
Note : Bien que les opérateurs binaires puissent être utilisés afin de représenter plusieurs valeurs booléennes avec un seul nombre en utilisant un masque de bits, c'est généralement une mauvaise pratique. En effet, JavaScript fournit d'autres moyens pour représenter un ensemble de valeurs booléennes comme les tableaux ou l'utilisation de propriétés nommées pour stocker ces valeurs. L'utilisation d'un masque de bit dégrade également la lisibilité, la clarté et la maintenabilité du code.
Il peut être nécessaire d'utiliser de telles techniques dans des environnements extrêmement contraints, pour gérer des limites de stockage local ou lorsque chaque bit transmis sur le réseau compte. Cette technique devrait uniquement être considérée comme dernière mesure pour réduire la taille.
Le type BigInt
Le type BigInt
est un type numérique qui permet de représenter des entiers avec une précision arbitraire. Avec ce type, on peut donc manipuler des entiers plus grands que ceux représentables avec Number
.
Pour créer un grand entier, on ajoutera un n
après l'entier ou on appellera le constructeur BigInt
.
On peut connaître la valeur la plus grande qui peut être incrémentée et représentée avec le type Number
en utilisant la constante Number.MAX_SAFE_INTEGER
. Avec les grands entiers, on peut manipuler des nombres qui vont au-delà de Number.MAX_SAFE_INTEGER
.
Dans l'exemple qui suit, on voit le résultat obtenu lorsqu'on incrémente la valeur de Number.MAX_SAFE_INTEGER
:
// BigInt
> const x = BigInt(Number.MAX_SAFE_INTEGER);
9007199254740991n
> x + 1n === x + 2n; // 9007199254740992n === 9007199254740993n
false
// Number
> Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2; // 9007199254740992 === 9007199254740992
true
À l'instar des nombres classiques, on peut utiliser les opérateurs +
, *
, -
, **
et %
. Un grand entier ne sera pas strictement égal à un nombre mais on pourra avoir une égalité faible.
Un grand entier se comportera comme un nombre lorsqu'il est converti en booléen avec if
, ||
, &&
, Boolean
et !
.
Il n'est pas possible d'utiliser des grands entiers et des nombres de façon interchangeable. Une exception TypeError
sera déclenchée en cas d'incompatibilité.
NaN
NaN
(pour Not A Number en anglais, qui signifie « qui n'est pas un nombre ») est utilisée lorsque le résultat d'une opération arithmétique ne peut pas être exprimée comme un nombre. Il s'agit également de la seule valeur JavaScript qui n'est pas égale à elle-même (du fait de la norme IEEE 754).
Le type chaîne de caractères (String
)
Ce type JavaScript est utilisé afin de représenter des données de texte. C'est un ensemble d'« éléments » de valeurs entières non-signées représentées sur 16 bits. Chaque élément occupe une position au sein de cette chaîne de caractères. Le premier élément est situé à l'indice 0
, le deuxième à l'indice 1
et ainsi de suite. La longueur d'une chaîne de caractères correspond au nombre d'éléments qu'elle contient.
À la différence d'autres langages (comme le C), les chaînes de caractères JavaScript sont immuables. Cela signifie qu'une fois une chaîne créée, il est impossible de la modifier.
En revanche, il est toujours possible de créer une autre chaîne basée sur la première grâce à des opérations. Par exemple :
- Un fragment de la chaîne originelle en sélectionnant certaines lettres ou en utilisant
String.substr()
. - Une concaténation de deux chaînes de caractères en utilisant l'opérateur de concaténation (
+
) ouString.concat()
.
Attention à ne pas utiliser les chaînes pour tout et n'importe quoi !
Ça peut être tentant de vouloir utiliser des chaînes afin de représenter des données complexes. En revanche, les avantages de cette méthode ne sont que très superficiels :
- On peut facilement construire des chaînes complexes grâce à la concaténation.
- On peut déboguer rapidement le contenu des chaînes de caractères.
- Les chaînes de caractères sont utilisées à de multiples endroits dans beaucoup d'API (champs de saisie, valeurs en stockage local, réponses
XMLHttpRequest
avecresponseText
, etc.).
En utilisant des conventions, il peut être possible de représenter n'importe quelle donnée sous forme d'une chaîne de caractères, en revanche cela n'est souvent pas la meilleure façon. Par exemple, avec un séparateur, on pourrait émuler le comportement d'un tableau en « interdisant » que ce séparateur soit utilisé pour éléments, etc. On pourrait ensuite définir un caractère d'échappement, qui serait à son tour inutilisable dans les chaînes : toutes ces pseudo-conventions entraîneront de lourdes conséquences en termes de maintenance.
En résumé, les chaînes doivent être utilisées pour les données de texte. Pour des données plus complexes, utilisez une abstraction adéquate et analysez/parsez les chaînes que vous recevez d'autres API.
Le type symbole
Un symbole est une valeur primitive unique et immuable pouvant être utilisée comme clé pour propriété d'un objet (voir ci-après). Dans d'autres langages de programmation, les symboles sont appelés atomes.
Pour plus de détails, voir les pages du glossaire et de Symbol
JavaScript.
Les objets
En informatique, un objet est une valeur conservée en mémoire à laquelle on fait référence grâce à un identifiant.
Propriétés
En JavaScript, les objets peuvent être considérés comme des collections de propriétés. En utilisant un littéral objet, il est possible d'initialiser un ensemble limité de propriétés ; d'autres propriétés peuvent ensuite être ajoutées et/ou retirées. Les valeurs des propriétés peuvent être de n'importe quel type, y compris des objets. Cela permet de construire des structures de données complexes. Les propriétés sont identifiées grâce à une « clé ». Une clé peut être une chaîne de caractères ou un symbole.
Il existe deux types de propriétés qui ont certains attributs : des propriétés de données (data property) et des propriétés d'accesseur.
Note : Chaque propriété est décrite par des attributs correspondants. Ceux-ci sont utilisés par le moteur JavaScript et ne peuvent pas être manipulés depuis le code. Pour les identifier, les attributs sont indiqués entre double crochets.
Voir la page Object.defineProperty()
pour en savoir plus.
Propriétés de données
Elles associent une clé avec une valeur et possèdent les attributs suivants :
Attribut | Type | Description | Valeur par défaut |
---|---|---|---|
[[Value]] |
N'importe quel type JavaScript | La valeur obtenue lorsqu'on accède à la propriété. | undefined |
[[Writable]] |
Booléen |
Si cet attribut vaut false , l'attribut [[Value]] de la propriété ne pourra pas être changé.
|
false |
[[Enumerable]] |
Booléen |
Si cet attribut vaut |
false |
[[Configurable]] |
Booléen |
Si cet attribut vaut false , la propriété ne peut pas être supprimée, ne peut pas être changée en propriété d'accesseur et les attributs en dehors de [[Value]] et [[Writable]] ne pourront pas être changés.
|
false |
Attribut | Type | Description |
---|---|---|
Read-only |
Booléen | État symétrique pour l'attribut ES5 [[Writable]] . |
DontEnum |
Booléen | État symétrique pour l'attribut ES5 [[Enumerable]] . |
DontDelete |
Booléen | État symétrique pour l'attribut ES5 [[Configurable]] . |
Propriétés d'accesseur
Ces propriétés associent une clé avec une ou deux fonctions accesseur et mutateur (respectivement get
et set
) qui permettent de récupérer ou d'enregistrer une valeur.
Note : Il est important de noter qu'on parle de propriété d'accesseur et pas de méthode. On peut donner des accesseurs semblables à ceux d'une classe à un objet en utilisant une fonction comme valeur d'une propriété mais ça ne fait pas de l'objet une classe.
Elles possèdent les attributs suivants :
Attribut | Type | Description | Valeur par défaut |
---|---|---|---|
[[Get]] |
Un objet Function ou undefined |
La fonction qui est appelée sans argument afin de récupérer la valeur de la propriété quand on souhaite y accéder. Voir aussi la page sur get . |
undefined |
[[Set]] |
Un objet Function ou undefined |
La fonction, appelée avec un argument qui contient la valeur qu'on souhaite affecter à la valeur et qui est exécutée à chaque fois qu'on souhaite modifier la valeur. Voir aussi la page sur set . |
undefined |
[[Enumerable]] |
Booléen | S'il vaut true , la propriété sera listée dans les boucles for…in . |
false |
[[Configurable]] |
Booléen | S'il vaut false , la propriété ne pourra pas être supprimée et ne pourra pas être transformée en une propriété de données. |
false |
Les objets « normaux » et les fonctions
Un objet JavaScript est un ensemble de correspondances entre des clés et des valeurs. Les clés sont représentées par des chaînes ou des symboles (Symbol
). Les valeurs peuvent être de n'importe quel type. Grâce à cela, les objets peuvent, naturellement, être utilisés comme tables de hachage.
Les fonctions sont des objets classiques à la seule différence qu'on peut les appeler.
Les dates
Lorsqu'on souhaite représenter des dates, il est tout indiqué d'utiliser le type utilitaire natif Date
de JavaScript.
Les collections indexées : les tableaux (Arrays) et les tableaux typés (Typed Arrays)
Les tableaux (ou Arrays en anglais) sont des objets natifs qui permettent d'organiser des valeurs numérotées et qui ont une relation particulière avec la propriété length
.
De plus, les tableaux héritent de Array.prototype
qui permet de bénéficier de plusieurs méthodes pour manipuler les tableaux. Par exemple, indexOf()
qui permet de rechercher une valeur dans le tableau ou push()
qui permet d'ajouter un élément au tableau. Les tableaux sont donc indiqués quand on souhaite représenter des listes de valeurs ou d'objets.
Les tableaux typés (Typed Arrays en anglais) ont été ajoutés avec ECMAScript 2015 et offrent une vue sous forme d'un tableau pour manipuler des tampons de données binaires. Le tableau qui suit illustre les types de données équivalents en C :
Type | Intervalle | Taille (exprimée en octets) | Description | Type Web IDL | Type équivalent en C |
Int8Array |
-128 à 127 | 1 | Entier signé en complément à deux sur 8 bits. | byte |
int8_t |
Uint8Array |
0 à 255 | 1 | Entier non signé sur 8 bits. | octet |
uint8_t |
Uint8ClampedArray |
0 à 255 | 1 | Entier non signé sur 8 bits (compris entre 0 et 255). | octet |
uint8_t |
Int16Array |
-32768 à 32767 | 2 | Entier signé en complément à deux sur 16 bits. | short |
int16_t |
Uint16Array |
0 à 65535 | 2 | Entier non signé sur 16 bits. | unsigned short |
uint16_t |
Int32Array |
-2147483648 à 2147483647 | 4 | Entier signé en complément à deux sur 32 bits. | long |
int32_t |
Uint32Array |
0 à 4294967295 | 4 | Entier non signé sur 32 bits. | unsigned long |
uint32_t |
Float32Array |
1.2x10^-38 à 3.4x10^38 | 4 | Nombre flottant sur 32 bits selon la représentation IEEE (7 chiffres significatifs). | unrestricted float |
float |
Float64Array |
5.0x10^-324 à 1.8x10^308 | 8 | Nombre flottant sur 64 bits selon la représentation IEEE (16 chiffres significatifs). | unrestricted double |
double |
BigInt64Array |
-2^63 à 2^63-1 | 8 | Nombre entier signé sur 64 bits en complément à deux. | bigint |
int64_t (signed long long) |
BigUint64Array |
0 à 2^64-1 | 8 | Nombre entier non signé sur 64 bits. | bigint |
uint64_t (unsigned long long) |
Les collections avec clés : Map
, Set
, WeakMap
, WeakSet
Ces structures de données utilisent des clés pour référencer des objets. Elles ont été introduites avec ECMAScript 2015. Set
et WeakSet
représentent des ensembles d'objets, Map
et WeakMap
associent une valeur à un objet.
Il est possible d'énumérer les valeurs contenues dans un objet Map
mais pas dans un objet WeakMap
. Les WeakMap
quant à eux permettent certaines optimisations dans la gestion de la mémoire et le travail du ramasse-miettes.
Il est possible d'implémenter des objets Map
et Set
grâce à ECMAScript 5. Cependant, comme les objets ne peuvent pas être comparés (avec une relation d'ordre par exemple), la complexité obtenue pour rechercher un élément serait nécessairement linéaire. Les implémentations natives (y compris celle des WeakMap
) permettent d'obtenir des performances logarithmiques voire constantes.
Généralement, si on voulait lier des données à un nœud DOM, on pouvait utiliser les attributs data-*
ou définir les propriétés à un même l'objet. Malheureusement, cela rendait les données disponibles à n'importe quel script fonctionnant dans le même contexte. Les objets Map
et WeakMap
permettent de gérer plus simplement une liaison « privée » entre des données et un objet.
Les données structurées : JSON
JSON (JavaScript Object Notation) est un format d'échange de données léger, dérivé de JavaScript et utilisé par plusieurs langages de programmation. JSON permet ainsi de construire des structures de données universelles pouvant être échangées entre programmes.
Pour plus d'informations, voir la page du glossaire et la page sur JSON
.
Les autres objets de la bibliothèque standard
JavaScript possède une bibliothèque standard d'objets natifs. Veuillez lire la référence pour en savoir plus sur ces objets.
Déterminer le type des objets grâce à l'opérateur typeof
L'opérateur typeof
peut vous aider à déterminer le type d'une variable. Pour plus d'informations et sur les cas particuliers, voir la page de référence sur cet opérateur.
Voir aussi
- Structures de données et algorithmes JavaScript par Oleksii Trekhleb (en anglais)
- Un ensemble de structures de données usuelles et d'algorithmes classiques, en JavaScript, par Nicholas Zakas (en anglais)
- Implémentations de différentes structures de données et utilitaires de recherche en JavaScript (en anglais)
- Types de données et valeurs dans la spécification ECMAScript (en anglais)