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 :


```js
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)
    + Le type booléen
    + Le type nul
    + Le type indéfini
    + Le type nombre
    + Le type pour les grands entiers
    + Le type pour les chaînes de caractères
    + Le type symbole
* 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

ECMAScript possède deux types numériques natifs : `Number` et `BigInt`, ainsi que la valeur spéciale `NaN`.

### 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 :


```js
> 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` :


```js
// 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 (`+`) ou `String.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` avec `responseText`, 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 :

Attributs d'une propriété de donnée| 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 `true`, la propriété sera énumérée dans les boucles `for…in`. Voir aussi Rattachement et caractère énumérable des propriétés. | `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)