Aller au contenu

Javascript • Système de types • Valeurs primitives

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 primitifs.

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. Le premier ensemble inclus :

Les valeurs primitives sont immuables. C’est-à-dire qu’une valeur primitive ne peut pas être modifiée après sa création. Il est bien entendu possible d’affecter une autre valeur primitive à une variable.

Les types primitifs sont des types valeur (value type) : une variable d’un type primitif contient directement la valeur.

L’opérateur typeof nous permet de connaitre le type actuel d’une variable.

let toto = "text";
console.log(typeof toto); // ← "string"
toto = 69;
console.log(typeof toto); // ← "number"
toto = true;
console.log(typeof toto); // ← "boolean"

Lorsque nous affectons une valeur primitive à une variable, Javascript lui associe l’objet wrapper correspondant à son type (à l’exception de null et undefined).

// → twelve est un nombre (number), l'objet Number est appliqué à twelve
const twelve = 12;
// → Nous avons accès aux méthodes d'instance de Number
// .toExponential(), .toFixed(), .toString(), etc.
const stringTwelve = twelve.toString(); // "12"
TypetypeofObject wrapper
null”object”-
undefined”undefined”-
boolean”boolean”Boolean
number”number”Number
string”string”String
symbol”symbol”Symbol

Chaque object wrapper nous permet de faire une conversion explicite de type.

const falsyNumber = Boolean(0); // → false
const truthyString = Boolean("abc"); // → true
const stringifiedNumber = String(123); // → "123"
const numberedString = Number("456"); // → 456

Le type null ne possède qu’une seule valeur : null. La valeur null représente l’absence intentionnelle de valeur. Nous pouvons aussi interpréter null par : « aucun objet ».

null est un mot-clef, ainsi une variable ne peut pas être nommée null.

null est une valeur falsy, c’est-à-dire qu’elle équivaut à false pour les opérations booléennes.

Le type undefined ne possède qu’une seule valeur : undefined.

Une variable déclarée et sans affectation sera undefined.

let toto;
console.log(toto); // ← undefined

Une fonction qui ne retourne aucune valeur retourne undefined.

// → La fonction log ne retourne aucune valeur
const log = (text) => {
console.log(text);
};
// → Un avertissement peut apparaitre pour cette instruction :
// "Void function return value is used"
const logResult = log("This is Sparta!");
console.log(logResult); // ← undefined

undefined est une valeur falsy, c’est-à-dire qu’elle équivaut à false pour les opérations booléennes.

Nous allons voir ensemble quelques exemples de différences et points communs entre null et undefined.

console.log(typeof null); // ← "object"
console.log(typeof undefined); // ← "undefined"
console.log(undefined === undefined); // ← true
console.log(null === null); // ← true
console.log(null === undefined); // ← false
console.log(null == undefined); // ← true
console.log(!null); // ← true
console.log(!undefined); // ← true
console.log(!!null); // ← false
console.log(!!undefined); // ← false
console.log(Number.isNaN(1 + null)); // ← false
console.log(Number.isNaN(1 + undefined)); // ← true

null et undefined sont des valeurs nullish. Ainsi, elles ont le même comportement sur les opérateurs de coalescence.

L’opérateur ?? renvoie l’opérateur de droite si l’opérande de gauche est nullish.

const notNull = "Non nul";
const nullish = undefined;
console.log(notNull ?? "Défaut"); // ← "Non nul"
console.log(nullish ?? "Défaut"); // ← "Défaut"

L’opérateur ??= évalue l’opérande de droite et l’attribue à gauche si l’opérande de gauche est nullish.

const person = { firstName: null, lastName: "Johnson" };
person.firstName ??= "(unknown)";
person.lastName ??= "(unknown)";
console.log(person); // ← Object { firstName: "(unknown)", lastName: "Johnson" }

L’opérateur ?. est similaire à l’opérateur de chainage ., à ceci près qu’au lieu de causer une erreur si une référence est nullish, l’évaluation s’arrête et retourne undefined.

// Vide la console, sans erreur
window.console.clear();
// → Uncaught TypeError: can't access property "clear", window.konsole is undefined
window.konsole.clear();
window.konsole?.clear(); // ← undefined

Le type boolean possède deux valeurs : false et true.

L’object wrapper associé à boolean est : Boolean.

Les opérateurs de comparaison ==, ===, !=, !==, <, <=, >, >= retournent un boolean.

Les opérateurs logiques AND (&&), OR (||), NOT (!) permettent d’effectuer des opérations sur les boolean.

Lorsque nous utilisons ces opérateurs, les valeurs non booléennes sont implicitement converties en boolean. Ainsi certaines valeurs sont converties en false, alors que d’autres sont converties en true. Nous parlons alors de valeurs falsy et de valeurs truthy.

Voici quelques valeurs falsy :

  • false
  • null
  • undefined
  • 0, +0, -0
  • NaN
  • '', ""

Voici quelques valeurs truthy :

  • true
  • {}
  • []
  • -3.14, -1, 1, 3.14
  • -Infinity, Infinity
  • new Date()
  • "false", "0", "a"

Le type number permet de stocker à la fois des nombres entiers et des nombres à virgule flottante.

L’object wrapper associé à number est : Number.

Les nombres à virgule flottante sont représentés sur 64 bits. Cette représentation permet de stocker des valeurs entre 2-1074 [Number.MIN_VALUE] et 2+1074 [Number.MAX_VALUE], aussi bien négatifs que positifs.

Les nombres entiers peuvent être stockés de façon sûre entre -(253 − 1) [Number.MIN_SAFE_INTEGER] et +(253 − 1) [Number.MAX_SAFE_INTEGER]. En dehors de cet interval, un nombre entier est converti en nombre à virgule flottante, perdant ainsi de sa précision. Nous pouvons utiliser la méthode Number.isSafeInteger() pour savoir si un entier possède une valeur fiable ou pas.

Si un nombre obtient une valeur en dehors des limites alors les conversions suivantes sont appliquées :

NaN (Not A Number) est une valeur spéciale indiquant que la valeur n’est pas un nombre. Voici quelques exemples d’opérations qui retourne NaN :

  • conversion implicite ou explicite impossible vers number : parseInt("blabla"), Number(undefined), Math.abs(undefined)
  • opération mathématique qui ne retourne pas un nombre réel : Math.sqrt(-1)
  • opération indéterminée avec Infinity : 0 * Infinity, Infinity / Infinity, Infinity - Infinity
  • opération implicite ou explicite avec Nan : 5 * NaN, NaN - 4, 3 * "blabla"

Il est possible de vérifier si un nombre est NaN Number.isNaN() ou isNaN().

console.log(NaN === NaN); // ← false
console.log(Number.NaN === NaN); // ← false
console.log(Number.NaN); // ← NaN
console.log(isNaN(NaN)); // ← true
console.log(Number.isNaN(NaN)); // ← true
console.log(isNaN(-0)); // ← false
console.log(isNaN(-3.14)); // ← false
console.log(isNaN(19)); // ← false

Le type string permet de stocker une chaine de caractères encodée en UTF-16.

L’object wrapper associé à string est : String.

Un texte (ou valeur littérale) peut être declaré :

  • entre simples quotes : ‘Maître Corbeau, sur un arbre perché, tenait en son bec un fromage.’ ;
  • entre doubles quotes : “Maître Renard, par l’odeur alléché, lui tint à peu près ce langage :” ;
  • entre accents graves (backtick) : `Et bonjour, Monsieur du Corbeau. Que vous êtes joli ! Que vous me semblez beau !`.

Un texte entre accents graves permet de définir un gabarit de littéral (template literal). Il est ainsi possible d’interpoler des expressions en utilisant ${}.

const animals = ["Lièvre", "Tortue"];
const fable = `Rien ne sert de courir ; il faut partir à point.
Le ${animals[0]} et la ${animals[1]} en sont un témoignage.`;
// → Rien ne sert de courir ; il faut partir à point.
// Le Lièvre et la Tortue en sont un témoignage.
console.log(fable);

Nous pouvons concaténer des chaines de caractères avec l’opérateur +.

const grasshopper = "La Cigale";
const ant = "La Fourmi";
console.log(grasshopper + " et " + ant); // ← La Cigale et La Fourmi

Nous pouvons faire un combo concaténation + affectation avec l’opérateur +=.

let fable = "Le Corbeau";
fable += " et Le Renard";
console.log(fable); // ← Le Corbeau et Le Renard

Javascript propose un typage dynamique et faible, nous évitant de trop nous soucier du type des données que nous manipulons.

Nous avons vu le premier ensemble de types : les types de valeurs primitives.

Dans l’article suivant, nous nous concentrerons sur les types objet.