Skip to content

Latest commit

 

History

History
2455 lines (2006 loc) · 85.4 KB

index.adoc

File metadata and controls

2455 lines (2006 loc) · 85.4 KB

Jouer avec JavaScript

Passons en revue les variables et structures ECMAScript pour mieux comprendre ce qui en fait un langage élégant et moderne.

Sommaire
  • Qu’est-ce que JavaScript ?

  • Comprendre l’évolution de la spécification ECMAScript

  • Jongler avec les différentes structures du langage

  • En savoir plus sur des éléments avancés du langage

JavaScript est souvent raillé. Parce que ce n’est pas un vrai langage. Parce qu’il a été créé en 6 jours. Parce qu’il n’est pas orienté objet.

JavaScript est un langage expressif qui a énormément gagné en maturité depuis les années 2010. Il se révèle parfaitement opérationnel dès lors que l’on s’intéresse à ses fonctionnalités, sans faire de hors-piste.

Les types de données et les méthodes de manipulations qu’elles nous offrent permettent d’écrire un code plus simple, à lire et à produire. Certaines structures de données nous aident à mieux organiser nos données ainsi qu’à mieux les traiter.

1. Qu’est-ce que JavaScript ?

Je vais vous présenter plusieurs exemples de code. Ils ont en commun d’être tous écrits en JavaScript.

intro/ecmascript.js
link:./examples/intro/ecmascript.js[role=include]

Cet exemple illustrait la création de variables, de chaînes de caractères. Ce sont des fonctionnalités de base de la spécification ECMAScript.

intro/web.js
link:./examples/intro/web.js[role=include]

fetch() ne fait pas partie de la spécification ECMAScript. C’est un ajout des navigateurs web. On parle alors d'API JavaScript pour le Web.

Note
Glossaire API (Interface de programmation)

Les API sont des interfaces pour dialoguer avec un programme ou une ressource informatique. Elles définissent des vocabulaires pour exécuter des actions spécifiques.

intro/dom.js
link:./examples/intro/dom.js[role=include]

La variable document et les méthodes querySelector et addEventListener font aussi partie des API JavaScript pour le Web. En l’occurrence, elles font partie de l’API DOM (Document Object Model), un mécanisme pour interagir avec une page web grâce à ECMAScript.

intro/node.js
link:./examples/intro/node.js[role=include]

Ce dernier exemple est spécifique à Node. Ce dernier propose la fonction require() pour charger des modules et interagir avec le système d’exploitation.

Autrement dit, ECMAScript est un langage, une grammaire avec des fonctionnalités de base. Chaque environnement – les navigateurs web, Node – le comprend et lui ajoute de nouvelles expressions, contextuelles à cet environnement d’exécution. JavaScript est le grand ensemble des technologies qui reposent sur ECMAScript pour fonctionner.

Note
Histoire À propos de JavaScript

JavaScript est inventé en 1995 par Brendan Eich alors qu’il est employé de la société Netscape Communications. Microsoft lui emboîte le pas en incluant JavaScript dans son logiciel Internet Explorer, alors en version 3. Pour des raisons de droits de marque, il y est dénommé JScript.

La spécification est ensuite validée par l’organisme Ecma International en juin 1997 sous le nom d’ECMAScript, standard ECMA-262.

Le terme JavaScript est resté dans le vocabulaire courant, mais, en fait, il s’agit bien d’ECMAScript.

Adobe Flash utilise un dérivé d’ECMAScript : ActionScript. Bien des machines virtuelles sont capables d’interpréter partiellement ou intégralement ECMAScript : Rhino, Konq, BESEN en Object Pascal ou encore Esprima, qui est elle-même écrite dans ce langage.

Si d’autres langages de programmation se cantonnent soit au côté client (VBScript, ActionScript, Elm), soit au côté serveur (Ruby, Python, Haskell), JavaScript a débuté côté client pour s’étendre aussi côté serveur. Un développeur ou une développeuse dite full stack programme des applications sur les deux fronts. Node a cet avantage d’unifier le langage de programmation entre les environnements client et serveur.

Le langage ECMAScript – appelons-le ainsi à partir de maintenant – a évolué au fil du temps. Il s’est enrichi de nouvelles fonctionnalités au fil des versions, mais aussi de sucres syntaxiques (raccourcis d’écriture) et de rigueur aussi, pour corriger des défauts de design.

Le comité de travail TC39 (Technical Committee, https://github.com/tc39) est en charge de l’évolution du langage, standardisé sous le doux sobriquet de standard ECMA-262, à charge ensuite aux différents implémenteurs de suivre les changements et de les incorporer dans leurs machines virtuelles.

Node se base sur la machine virtuelle V8 de Google pour interpréter les expressions ECMAScript. De fait, Node comprend les mêmes expressions ECMAScript que V8.

Nous verrons un peu plus tard dans ce chapitre comment suivre la compatibilité de Node avec ECMAScript. Intéressons-nous à l’évolution du langage et à ce que ça nous apporte.

1.1. ECMAScript 5 (aka ES5)

ECMAScript a été standardisé dans sa version 5 en décembre 2009. La révision 5.1 de juin 2011 est une correction mineure de la spécification.

Il s’agit d’une évolution majeure dans l’histoire du langage. La précédente version – ECMAScript 3 – était âgée de dix ans.

ECMAScript 5 limite drastiquement certains effets indésirables du langage grâce au mode strict. De nouvelles méthodes de manipulation de tableaux et d’objets voient le jour, ainsi qu’une prise en charge native du format de données JSON.

La standardisation de cette version d’ECMAScript a contribué à redorer l’image du langage, mais aussi à faire émerger de nouvelles pratiques de programmation.

compat table
Figure 1. Table de compatibilité

1.2. ECMAScript 2015 (aka ES6 puis ES2015)

La spécification ECMAScript 2015 (ES2015) a été publiée en juin 2015. Elle succède à ECMAScript 5 après six années de gestation. Cette version a successivement été appelée ECMAScript Harmony, ECMAScript 6, puis ECMAScript 2015.

De nombreuses idées ont été piochées dans le langage CoffeeScript (http://coffeescript.org). Et surtout, un nouveau type d’outillage s’est formé pour commencer à utiliser ce JavaScript du futur avec le compilateur traceur de Google dès 2011 (https://github.com/google/traceur-compiler), puis avec le projet indépendant 6to5 dès 2014. Ce dernier a été renommé en Babel (https://babeljs.io) et son instigateur a par la suite été embauché par Facebook.

La pratique de compiler du JavaScript en JavaScript était en rupture avec ce qui se faisait précédemment : attendre qu’une fonctionnalité soit adoptée par un dénominateur commun de navigateurs web pour s’en servir. Cette fois-ci, on pouvait se servir du futur, dès aujourd’hui.

De fait, il n’y a pas eu à attendre six ans et les différentes implémentations pour profiter de ce qu’il y avait de meilleur.

Le prix à payer ? Un ticket d’entrée plus élevé lié à la maîtrise de l’outillage associé.

Table de compatibilité (navigateurs web)

https://kangax.github.io/compat-table/es6/

Table de compatibilité (Node.js)

https://node.green/

Spécification

https://www.ecma-international.org/ecma-262/6.0/

node green
Figure 2. Illustration de l’évolution de la compatibilité ECMAScript au fil des versions de Node

1.3. ECMAScript 2016, etc. (aka ES2016)

Depuis la sortie d’ECMAScript 2015, l’intention est de publier une nouvelle spécification par an, de travailler les fonctionnalités une par une pour ne pas attendre trop longtemps avant de les ratifier. En conséquence, les nouvelles versions annuelles sont beaucoup plus incrémentales. Elles se font moins attendre et contiennent moins de grands bouleversements.

Les fonctionnalités en cours de préparation sont listées dans le dépôt GitHub suivant : https://github.com/tc39/proposals. Le dernier stade avant la validation est le stage 3. Dès qu’une fonctionnalité passe en stage 4, elle est incluse dans la prochaine version d’ECMAScript – ECMAScript 2024 une fois l’année 2024 terminée.

Les fonctionnalités approuvées sont consignées dans un document : https://github.com/tc39/proposals/blob/master/finished-proposals.md.

2. Éléments de base du langage

Cette section décrit les notions nécessaires pour s’approprier le reste des exemples de l’ouvrage. On apprendra notamment à créer des variables, à naviguer dans des listes d’éléments et à faire la différence entre un objet et une fonction.

2.1. Les types de données

Qu’entend-on par type de données ? Faisons-nous notre propre idée avec une suite d’exemples. Ces notions seront développées dans le reste du chapitre, pour mieux comprendre ce que l’on peut en faire.

base/string.js
link:./examples/base/string.js[role=include]

Une valeur entourée de guillemets est considérée par l’interpréteur ECMAScript comme une chaîne de caractères, du texte.

Ces guillemets sont selon les cas des guillemets simples ('), doubles (") ou obliques (`).

On peut effectuer des opérations d’identification ou d’assemblage avec une valeur de type chaîne de caractères.

base/number.js
link:./examples/base/number.js[role=include]

ECMAScript considère les entiers (3 dans cet exemple) et les réels (12.3 dans cet exemple) comme des nombres. Il ne fait pas de distinction entre les deux.

On peut effectuer des opérations mathématiques entre plusieurs valeurs de type nombre.

base/boolean.js
link:./examples/base/boolean.js[role=include]

ECMAScript considère deux valeurs pour signifier vrai ou faux : respectivement true et false.

On peut effectuer des opérations logiques avec une valeur de type booléen.

base/null.js
link:./examples/base/null.js[role=include]

On utilise null pour signifier l'absence de valeur.

base/undefined.js
link:./examples/base/undefined.js[role=include]

La valeur undefined est utilisée pour signifier qu’une valeur est inconnue. Rares sont les cas où on choisira ce type de données par nous-même.

mdn::javascript[Data_structures, title="Primitives", text="primitives"]

Il existe trois autres types de données qui se basent sur ces types dits primitifs. Ils sont destinés à ranger, à classer et à exprimer de nouvelles valeurs en fonction d’autres.

base/array.js
link:./examples/base/array.js[role=include]

Un tableau se déclare en encadrant une suite de valeurs entre crochets. Il est capable de contenir n’importe quel type de valeurs et autant que nécessaire. L’ordre des valeurs a généralement une importance.

On peut effectuer des opérations de tri et de sélection avec un tableau de valeurs.

base/object.js
link:./examples/base/object.js[role=include]

Un objet se déclare en encadrant une suite de paires clé/valeur entre accolades. Il fonctionne comme un dictionnaire : on associe une valeur (type au choix) à une clé (un intitulé, un label). L’ordre des paires n’a généralement pas d’importance.

On peut effectuer des opérations de sélection avec un objet de valeurs.

base/function.js
link:./examples/base/function.js[role=include]

Une fonction accepte des arguments, de n’importe quel type et autant que nécessaire. Elle doit être déclarée pour être exécutée (dernière ligne de l’exemple précédent).

Une fonction retourne un résultat explicite avec le mot-clé return. Dans le cas contraire, ECMAScript considère que la valeur retournée équivaut implicitement à undefined.

On peut effectuer des opérations de transformation avec une fonction.

Les fonctions sont destinées à être appelées, pour effectuer des traitements répétitifs. Dès que l’on doit écrire deux fois la même chose, on l’écrit dans une fonction qu’on appelle deux fois.

2.2. Les variables

Les variables servent à ranger des valeurs. On peut ainsi les réutiliser plus tard, les transmettre et prendre des décisions en fonction de ce qu’elles contiennent.

Les variables nous aident à donner du sens à notre code, à le rendre intelligible par d’autres personnes ainsi qu’à nommer des choses comme on le ferait dans notre quotidien.

base/variables.js
link:./examples/base/variables.js[role=include]

À votre avis, quel est le prix du livre calculé dans l’exemple précédent ? Il suffit de suivre le chemin que prend la nouvelle valeur rangée dans la clé price de l’objet book, calculée par la fonction double à laquelle on passe la valeur contenue dans la variable base_price.

Le mot-clé const nous a servi à déclarer une variable. On ne peut étiqueter ainsi une variable avec le même nom qu’une seule fois. L’exemple suivant générera une erreur lors de la deuxième affectation :

variables/const.js
link:./examples/variables/const.js[role=include]
Note
Question Une variable constante ?

const est un type de variable qui ne peut être ni redéclaré ni être réaffecté : c’est en ça qu’il est constant.

Une variable const n’empêche pas la modification de son contenu, dans le cas d’un tableau ou d’un objet. La fonction Object.freeze() est une réponse adaptée pour garantir son immuabilité.

variables/const-freeze.js
link:./examples/variables/const-freeze.js[role=include]
  1. Affiche [ 'a', 'b', 'c' ].

  2. L’objet table est gelé grâce à la fonction Object.freeze().

  3. Cette ligne lance une erreur car nous tentons de modifier un objet gelé.

2.3. Les instructions

Des instructions nous servent à suivre, éviter ou répéter des chemins dans notre code.

L’instruction if exécute du code s’il remplit une condition. Cette dernière peut être une valeur ou une expression interprétée pour savoir à quel booléen elle correspond.

instructions/if.js
link:./examples/instructions/if.js[role=include]

L’exemple précédent vérifie que les deux conditions sont remplies (opérateur &&) pour afficher un message en conséquence.

On notera au passage que book.title n’est pas un booléen. ECMAScript regarde dans ce cas que la chaîne de caractères contient au moins un caractère. On expliquera ce comportement plus en détail dans la section “Jongler avec des valeurs vraies ou fausses”.

L’instruction if peut être complétée avec l’instruction else pour exécuter du code qui répondrait au cas contraire. Il est possible d’imbriquer plusieurs else if à la suite.

instructions/else-if.js
link:./examples/instructions/else-if.js[role=include]

Notre exemple n’empruntera qu’un seul des chemins, mais on constate qu’on pourrait en emprunter un autre en modifiant la valeur des clés title et published.

2.4. La portée (scope)

La portée est un concept très présent dans ECMAScript. On y fait souvent référence en parlant de variable globale et de variable locale. C’est une sorte de frontière d’accès à la valeur d’une variable.

scopes/local.js
link:./examples/scopes/local.js[role=include]

Dans cet exemple, la variable secret de type fonction a une portée globale au script. En revanche, la variable mot est définie dans la fonction secret et n’est donc pas accessible en dehors de la portée de la fonction. À l’inverse, ce qui est défini en dehors d’une fonction est accessible à l’intérieur d’une fonction.

La portée de la variable mot est locale à la fonction secret.

scopes/global.js
link:./examples/scopes/global.js[role=include]

Ici, nous illustrons la portée globale de la variable year. Elle est définie un cran au-dessus des fonctions next et nextYear. On peut y accéder, comme en atteste le code de la fonction nextYear.

À l’inverse, la variable value a une portée locale – elle est passée en paramètre de la fonction next. ECMAScript génèrera une erreur si on tente d’y accéder en dehors de sa portée.

La portée est délimitée par les fonctions. En l’absence de fonction, la portée maximale est celle du module (script) dans lequel la variable est déclarée.

Il existe un deuxième type de portée : la portée lexicale. L’exemple suivant servira à illustrer la nature de sa délimitation.

scopes/lexical.js
link:./examples/scopes/lexical.js[role=include]

Le mot-clé const crée une variable certes, mais une variable dont la portée est lexicale. La portée lexicale est délimitée par le bloc d’instructions dans lequel la variable est déclarée.

Ainsi, la variable price n’existe que dans le cadre du bloc if.

La portée lexicale sert à déclarer des variables sans "polluer" le reste du script, pour que son existence soit oubliée aussitôt le bloc exécuté.

3. Jongler avec du texte (chaînes de caractères)

Il est commun d’avoir à travailler avec des chaînes de caractères. Elles servent à stocker des URL, des titres, des identifiants, des tweets, des messages et des textes longs, entre autres.

string/base.js
link:./examples/string/base.js[role=include]
  1. Utilisation de guillemets simples (\n sert à revenir à la ligne).

  2. Utilisation de guillemets doubles : évite d’échapper le guillemet simple.

  3. Utilisation de guillemets obliques : autorise l’écriture sur plusieurs lignes.

Tous les caractères sont utilisables : lettres, chiffres, caractères accentués, émojis et même des sinogrammes ou des kanjis. Autrement dit, il n’y a pas de limite. Les environnements d’exécution se représentent les caractères au format UTF-16 (tables de stockage Unicode sur 16 bits de données).

Il est fréquent d’avoir à concaténer des chaînes de caractères, ou à les composer à partir d’une autre variable.

string/concat.js
link:./examples/string/concat.js[role=include]

Toute chaîne de caractères offre un ensemble d'attributs (.quelque-chose) et de méthodes (.autre-chose()) pour en savoir plus sur la chaîne mais aussi pour la transformer.

Par exemple, on connaît la longueur d’une chaîne via son attribut length.

string/length.js
link:./examples/string/length.js[role=include]
  1. Affiche 14.

  2. Affiche 0.

On accède à un caractère spécifique en utilisant la chaîne comme un tableau, ou à l’aide d’une méthode dédiée :

string/char-at.js
link:./examples/string/char-at.js[role=include]
  1. Affiche N.

  2. Affiche o.

Warning
Attention

Le premier caractère d’une chaîne est à l’index 0 et non pas à 1.

Deux autres fonctions transforment un texte en lettres minuscules ou majuscules :

string/lower-upper-case.js
link:./examples/string/lower-upper-case.js[role=include]
  1. Affiche node.js.

  2. Affiche NODE.JS.

D’autres fonctions nettoient ou complètent les espaces autour, au début ou à la fin d’une chaîne de caractères :

string/trim-pad.js
link:./examples/string/trim-pad.js[role=include]
  1. Affiche Node.js.

  2. Affiche Node.js  .

  3. Affiche   Node.js.

  4. Affiche BARCGB22XXX.

Dans cet exemple, la méthode padEnd complète jusqu’à 11 caractères, avec la lettre X. La méthode padStart fait la même chose mais avec le début de la chaîne.

indexOf retourne la position de la première occurrence dans une chaîne d’une sous-chaîne passée en paramètre. Si la valeur n’est pas trouvée, la méthode renvoie la valeur -1. À l’inverse, lastIndexOf retournera la dernière occurrence trouvée :

string/index-of.js
link:./examples/string/index-of.js[role=include]
  1. Retourne 4.

  2. Retourne -1 – aucune occurrence n’a été trouvée.

  3. Retourne 5 – première occurrence de la lettre a.

  4. Retourne 7 – dernière occurrence de la lettre a.

3.1. Expressions régulières (RegExp)

Si indexOf et lastIndexOf identifient des caractères exacts, comment faire lorsque l’on souhaite chercher de manière approximative, plusieurs fois et selon certaines conditions ?

Les expressions régulières (RegExp, pour Regular Expressions) entrent en jeu dans ces cas plus avancés. Leur mécanisme décrit des motifs à identifier. Plusieurs méthodes servent ensuite à tester, identifier et remplacer ces motifs au sein d’une chaîne de caractères.

Tip
Anecdote RegExp et Perl

La syntaxe d’expressions régulières est inspirée de celle du langage de programmation Perl (https://www.perl.org) dans sa version 5.

Une expression régulière est décrite le plus souvent en tant que motif encadré par des barres obliques, suffixé d'options exprimées sous forme de lettres :

regexp/base.js
link:./examples/regexp/base.js[role=include]

Cet exemple utilise l’option i mais il en existe plusieurs :

Insensible à la casse (i)

On souhaite identifier du contenu, peu importe s’il est en majuscules ou non.

Multiligne (m)

La recherche s’effectue sur toutes les lignes.

Global (g)

La recherche identifie tous les résultats – au lieu du seul premier.

Unicode (u)

S’utilise si le motif de recherche exprime des séquences de caractères Unicode sous la forme \u{…​} (voir les classes de caractères ci-après).

Illustrons leur utilisation en identifiant du texte répondant (match) à une expression régulière (/…​/) :

regexp/flags.js
link:./examples/regexp/flags.js[role=include]
  1. Identifie et affiche Node, l’occurrence contenue dans le mot Node.js.

  2. Affiche deux fois ode – les occurrences contenues dans les mots Node.js et Anode.

  3. Affiche Node et node en combinant les deux options i et g – les occurrences contenues dans les mots Node.js et Anode.

Des éléments de syntaxe complètent les options pour identifier des motifs au sein de chaînes de caractères :

Ensemble de caractères (entre [ et ])

Liste l’ensemble des caractères recherchés. Le caractère - indique une plage de caractères. (ex. [a-d] correspond à [abcd], donc a ou _b_ ou _c_ ou _d_).

Nombre de caractères (entre { et })

Répète un caractère ou une sous-chaîne ; exactement ({2} – exactement deux fois), au moins ({2,} – au moins deux fois) ou entre ({1,2} – entre une et deux fois).

Nombre de caractères (?+ et *)

Version raccourcie du nombre de caractères pour des besoins usuels : 0 ou 1 caractère avec ?, 1 caractère et plus avec + et 0 caractère et plus avec *.

regexp/syntax.js
link:./examples/regexp/syntax.js[role=include]
  1. Affiche ["75"] – les 2 premiers caractères numériques de la chaîne.

  2. Affiche ["75015"] – les 5 premiers caractères numériques (satisfait la condition 5 de {2,5}).

  3. Affiche ["75015 Paris"].

  4. Affiche ["75015 Paris"] – l’option i évite de préciser l’ensemble A-Z.

  5. Affiche ["92410 Ville"] – capture les caractères jusqu’à ce que la condition ne soit plus remplie en rencontrant le trait d’union (-).

  6. Affiche ["92410 Ville-d’Avray"].

D’autres opérateurs délimitent notre recherche :

Début et fin de chaîne (^ et $)

Quand l’option multiligne (m) est utilisée, les notions de début et de fin s’appliquent au niveau de la ligne.

Limite de mot (\b)

Symbolise tout caractère ne faisant pas partie d’un mot, y compris le début ou la fin d’une chaîne.

Ou (|)

Sépare deux choix (ex. /noir|blanc/).

Groupe de capture (entre ( et ))

Délimite un groupe de caractères. Les groupes peuvent par la suite être identifiés et remplacés.
On notera également que l’emploi des groupes change la structure des résultats en un tableau de plusieurs éléments, de la forme ["chaîne identifiée", "groupe 1", "groupe 2" …​].

regexp/limits.js
link:./examples/regexp/limits.js[role=include]
  1. Affiche ["75015 Paris"].

  2. Affiche ["75015 Paris", "75015"] – le premier élément correspond à la chaîne identifiée tandis que le second correspond au premier groupe de capture.

  3. Affiche ["33900", "33900"] – l’option multiligne itère de ligne en ligne jusqu’à trouver un motif.

  4. Affiche ["33900", "33074", "33700"] – l’option multiligne globale retourne tous les groupes de capture.

On notera qu’il faut faire attention à ce que l’on regarde : le format de résultat varie selon qu’on utilise ou non des groupes de capture et selon qu’on utilise l’option globale ou multiligne.

Des symboles servent de raccourcis pour désigner plusieurs caractères simultanément :

Tout caractère (.)

tout caractère sauf le saut de ligne.

Caractère de mot (\w)

Tout caractère pouvant composer un mot anglais : les caractères accentués ne sont pas englobés (identique à [A-Za-z0-9_]).

Caractère numérique (\d)

Identique à [0-9].

Caractère d’espacement (\s)

Tout caractère d’espacement : espace, tabulation, retour chariot, etc.

Caractère Unicode (\u{…​})

Doit être combiné avec l’option u (/…​/u). Exemple : ♥︎ → \u{2665}.

Les alternatives de classes en majuscules sont des négations. \W pour "tout sauf un caractère de mot", \S pour "tout sauf un caractère d’espacement", etc.

regexp/classes.js
link:./examples/regexp/classes.js[role=include]
  1. Affiche ["♥ RegExp", "RegExp"] – et s’arrête là car l’espace suivant n’est pas un caractère de mot.

  2. Affiche "I ♥ 2024" – on a extrait le début de la phrase et l’année placée en fin de chaîne.

mdn::global[RegExp, title="Expressions régulières", text="les expressions régulières"]

La méthode test est pratique si la seule chose qui vous intéresse est de tester si une chaîne correspond à un motif :

regexp/test.js
link:./examples/regexp/test.js[role=include]

Enfin, la méthode replace est très utile pour transformer des chaînes de caractères, surtout en combinaison avec les groupes de capture :

regexp/replace.js
link:./examples/regexp/replace.js[role=include]
  1. Affiche "I love JavaScript" – si le premier argument de replace est une chaîne, elle est convertie automatiquement en expression régulière.

  2. Affiche "I ♥ PHP".

  3. Affiche "JavaScript ♥ me" – les symboles $<numéro> représentent les groupes de capture, qu’on place dans l’ordre de notre choix.

Le second argument accepte une fonction pour procéder à des remplacements dynamiques :

regexp/replace-function.js
link:./examples/regexp/replace-function.js[role=include]
  1. Affiche "I ♥ JAVASCRIPT" – le dernier mot est transformé en majuscules.

4. Jongler avec des valeurs vraies ou fausses (booléens)

Un booléen est un élément logique dont la valeur est soit true soit false, c’est-à-dire respectivement vrai ou faux. Ce type de valeur sert à exprimer des résultats de condition ("si ça alors … sinon") ainsi qu’à affirmer ou infirmer quelque chose.

boolean/base.js
link:./examples/boolean/base.js[role=include]
  1. Affiche 3.

  2. Affiche true – la condition est vérifiée.

  3. Affiche true – c’est la valeur de la variable check suite à son affectation à la ligne précédente.

  4. Affiche true.

Une donnée d’un autre type peut être convertie en booléen. La logique qui déterminera si la conversion retournera true ou false est la suivante :

true

Toute valeur non nulle.

false

Toute valeur nulle (null, 0NaN), vide ('') ou indéfinie (undefined).

boolean/convert.js
link:./examples/boolean/convert.js[role=include]
  1. Affiche false – il s’agit d’une chaîne vide.

  2. Affiche true – il s’agit d’une valeur non nulle.

  3. Affiche true – le tableau est vide mais l’objet en lui-même vaut quelque chose : un tableau.

5. Jongler avec des valeurs numériques (Number, Math)

ECMAScript ne fait pas de distinction entre des entiers et des nombres contenant des décimales : ce sont des nombres un point c’est tout.

number/base.js
link:./examples/number/base.js[role=include]
  1. Affiche true – les deux valeurs sont strictement équivalentes.

  2. Affiche false – un élément entre guillemets est une chaîne de caractères, pas un nombre.

Les nombres sont représentés par défaut en base 10. La plage de nombres utilisable dans un programme est définie par des constantes ECMAScript :

number/constants.js
link:./examples/number/constants.js[role=include]
  1. Affiche Infinity.

  2. Affiche -Infinity.

  3. Affiche 1.7976931348623157e+308 – le plus grand réel utilisable.

  4. Affiche 5e-324 – le plus petit réel utilisable.

  5. Affiche 9007199254740991 – le plus grand entier utilisable.

  6. Affiche -9007199254740991 – le plus petit entier utilisable.

Il est aussi possible de compter dans d’autres bases, notamment en hexadécimal (base 16). Cette dernière est exprimée en préfixant la valeur par 0x et avec les caractères de 0 à F – 0 à 9 puis A (vaut 10), B (vaut 11), etc.

number/hexa.js
link:./examples/number/hexa.js[role=include]
  1. Affiche 0.

  2. Affiche 10 – car A en hexadécimal vaut 10 en décimal.

  3. Affiche 160 – pour 10×16 (une “dizaine” vaut 16).

  4. Affiche 2560 – pour 10×16×16 (une “centaine” vaut 16×16).

Note
Rumeur JavaScript est nul en virgule flottante !

ECMAScript est souvent décrié pour son incapacité à gérer les opérations mathématiques avec précision.

0.2 + 0.6

ECMAScript respecte le standard IEEE 754 de gestion de nombres à virgule flottante sur 64 bits de données. Qui d’autre l’utilise ? D’autres langages "inconnus" comme Python, PHP et Ruby, entre autres.

5.1. Opérations mathématiques

Les nombres s’utilisent pour effectuer des opérations mathématiques. Chaque opération est dotée d’un symbole :

Opération Symbole

addition

+

soustraction

-

multiplication

*

division

/

modulo (reste de division)

%

exposant (puissance)

**

number/operations.js
link:./examples/number/operations.js[role=include]
  1. Affiche 6.

  2. Affiche -2.

  3. Affiche 8.

  4. Affiche 0.5.

  5. Affiche 2.

  6. Affiche 16.

5.2. Les nombres qui n’en sont pas (NaN)

Caution
Attention Opérations exotiques

Est-ce que vous avez déjà tenté d’additionner un nombre avec un tableau ? Pas forcément, mais ECMAScript ne vous en empêchera pas.

number/operations-types.js
link:./examples/number/operations-types.js[role=include]
  1. La chaîne '1' sera convertie en nombre (voir plus loin).

  2. On nous a toujours interdit la division par zéro ; ici, on affiche Infinity.

  3. Affiche aussi Infinity.

  4. Seule cette opération retourne NaN.

  5. Affiche 10.

  6. Affiche 11 — la valeur true est implicitement convertie en un entier.

Certaines opérations n’aboutiront pas mais n’afficheront pas d’erreur pour autant. Dans ce cas, leur résultat vaudra NaN pour not a number (littéralement : "n’est pas un nombre").

number/nan.js
link:./examples/number/nan.js[role=include]

La fonction Number.isNaN() nous aidera à vérifier si la valeur d’une variable ou le résultat d’une opération est un NaN ou non. Cette fonction retourne un booléen.

number/is-nan.js
link:./examples/number/is-nan.js[role=include]
  1. Affiche true.

  2. Affiche true.

  3. Affiche false.

  4. Affiche false.

Warning
Assertion NaN n’est pas un nombre ?

Il faut se méfier de NaN comme de la peste, car il est considéré comme un nombre du point de vue d’ECMAScript. Toute opération mathématique impliquant NaN renverra un NaN :

number/nan-number.js
link:./examples/number/nan-number.js[role=include]
  1. Affiche 'number'.

  2. Affiche 'number'.

Il vaut mieux s’assurer qu’une variable est à la fois un nombre et qu’elle ne vaut pas NaN :

number/is-not-a-nan.js
link:./examples/number/is-not-a-nan.js[role=include]

5.3. Convertir en nombre

indexterm[nombre, conversion]

Les lignes qui précédent l’évoquent un peu : on peut passer d’autres types de données à des nombres. Idéalement, on voudra transformer explicitement quelque chose en un nombre.

Pour cela nous disposons de deux fonctions :

  • parseInt essaie d’interpréter un nombre entier.

  • parseFloat essaie d’interpréter un nombre à virgule. La fonction s’arrête dès qu’elle n’a plus affaire à un chiffre.

number/parse.js
link:./examples/number/parse.js[role=include]
  1. Affiche 3.

  2. Affiche 3.141592653589793.

  3. Affiche 14 – ça ne change rien pour parseInt.

  4. Affiche 14.1 – la fonction s’arrête à la décimale précédant une lettre.

parseInt a cette particularité que l’on peut choisir la base de la conversion avec le second argument de la fonction.

number/parse-int.js
link:./examples/number/parse-int.js[role=include]
  1. Affiche 16.

  2. Affiche 10A vaut 10 en hexadécimal.

  3. Affiche 2560 – aurait pu s’écrire 0xA00.

5.4. Formater et arrondir des nombres

Si l’envie vous prenait de vouloir arrondir des nombres, il existe quelques fonctions pour vous aider :

Math.round()

Arrondit à l’entier le plus proche.

Math.ceil()

Arrondit à l’entier supérieur du nombre donné.

Math.floor()

Arrondit à l’entier inférieur du nombre donné.

number/round.js
link:./examples/number/round.js[role=include]
  1. Affiche 3.

  2. Affiche 4.

  3. Affiche 4.

  4. Affiche 4.

  5. Affiche 3.

Enfin, on peut préserver le formatage du nombre de décimales après la virgule en transformant le nombre en chaîne de caractères grâce à la méthode toFixed() :

number/to-fixed.js
link:./examples/number/to-fixed.js[role=include]
  1. Affiche '10.01'.

  2. Affiche '10'.

6. Créer et réutiliser des blocs de code (fonctions)

Une fonction est un bloc de code réutilisable et paramétrable. Elle retourne un résultat dont la valeur se calcule en fonction des paramètres que nous lui passons.

Cela se passe en deux temps :

  1. la création de la fonction ;

  2. l'exécution.

ECMAScript fournit un ensemble de fonctions de base : console.log(), setTimeout(), etc. Node ajoute les siennes (comme require()). Nous avons la liberté d’en créer nous-mêmes, spécifiques à nos besoins.

functions/base.js
link:./examples/functions/base.js[role=include]
  1. On crée la fonction hello.

  2. Affiche [Function: hello] – il s’agit de la définition de la fonction.

  3. Affiche "Hello World" – il s’agit de l'exécution de la fonction, qui retourne un résultat.

  4. Affiche un nombre aléatoire entre 0 et 100 – cette fonction est invoquée sans paramètre.

L’exemple précédent nous indique qu’une fonction se découpe en trois parties :

Les arguments

C’est la partie à gauche de la flèche (). Les arguments sont séparés par des virgules.

Le corps

C’est la partie entre accolades. Quand la fonction est sur une ligne, le résultat de l’opération est implicitement retourné. On peut dans ce cas se passer du mot-clé return.

La valeur de retour

C’est la valeur renvoyée en dehors de la fonction. Elle est définie à l’aide du mot-clé return. La valeur undefined est retournée de manière implicite lorsque ce dernier est absent.

Tip
Rappel Variables et portée

Le corps d’une fonction constitue une portée : toute variable définie dans le corps d’une fonction est invisible en dehors.

6.1. Les fonctions anonymes

Les fonctions anonymes sont employées en arguments d’autres fonctions. On les dit anonymes, car elles ne sont pas consignées dans des variables. Il est fréquent de les utiliser pour itérer sur des tableaux, lors d’événements ou dans des promesses.

C’est une manière élégante d’encapsuler du code à exécuter plus tard.

functions/anonymous.js
link:./examples/functions/anonymous.js[role=include]
  1. Affiche "Deux secondes plus tard" deux secondes après le début du script.

  2. Affiche "Le processus se termine" quand le processus se termine, une fois que toutes les actions en attente ont été exécutées.

6.2. Les fonctions de rappel (callback)

Quand une fonction est passée en argument d’une autre fonction, on appelle cela un callback. On l’appelle plus tard (to call back) que le moment où elle est définie. Elle reçoit des paramètres qui aident à reconstruire un contexte au moment de son exécution.

functions/callback.js
link:./examples/functions/callback.js[role=include]
  1. Le troisième argument (et les suivants) de setTimeout() sont transmis en paramètres de la fonction de rappel (callback).

  2. Cette fonction est invoquée une seconde après le début du script, et reçoit en paramètre la date du moment.

  3. Affiche l’année de la date passée en argument – dans cet exemple, l’année en cours.

6.3. Paramètres du reste (rest parameters)

Les paramètres du reste sont un nombre indéfini de paramètres regroupés dans un même tableau.

functions/rest.js
link:./examples/functions/rest.js[role=include]
  1. Affiche "On a compté 3 patates.".

7. Lister, filtrer et trier des éléments (Array)

Les tableaux (ou listes indexées) servent à lister des éléments, de tout type et dans l’ordre de notre choix. Chaque élément de tableau se voit attribuer un numéro (index) qui sert à le retrouver, en itérant à l’aide de boucles ou en ayant recours à d’autres méthodes d’identification.

array/base.js
link:./examples/array/base.js[role=include]
  1. Affiche ["lundi", "mardi", "mercredi", "jeudi", "vendredi"].

  2. Affiche 5 – soit la longueur du tableau.

  3. Affiche "mardi" – un tableau commence à l’index 0.

  4. Affiche undefined – il n’y a aucun élément défini à l’index 5.

L’exemple précédent illustre plusieurs caractéristiques des collections :

  • La numérotation débute à l’index 0.

  • La propriété length contient la longueur du tableau.

  • La valeur undefined est retournée quand on tente d’accéder à un index qui n’existe pas.

7.1. Créer des tableaux à partir d’autres valeurs

indexterm[tableau, Array.from()]

La fonction Array.from() est une manière de créer un tableau à partir de quelque chose qui ressemble à un tableau.

array/from.js
link:./examples/array/from.js[role=include]
  1. Affiche ["f", "r", "o", "m", "a", "g", "e"] – chaque lettre de la chaîne.

  2. Affiche ["F", "R", "O", "M", "A", "G", "E"] – chaque lettre de la chaîne a été passée en majuscule.

Le deuxième argument de Array.from() est facultatif. C’est une fonction anonyme qui s’utilise comme les méthodes d’itération Array.forEach() et Array.map().

Cette méthode est des plus utiles pour itérer sur des listes d’éléments DOM obtenues avec les fonctions document.querySelectorAll() et document.getElementsByTagName(), entre autres.

link:./examples/array/node-list.js[role=include]

7.2. Combiner des tableaux

Il est relativement aisé de composer des tableaux en fonction d’autres tableaux. Une première manière d’y parvenir est d’utiliser la méthode concat() :

array/concat.js
link:./examples/array/concat.js[role=include]
  1. Affiche ["Athos", "Porthos", "Aramis", "d’Artagnan", "Albert"].

Cette méthode crée un nouveau tableau à partir de deux passés en paramètres.

Note
Alternative Opérateur …​ (spread)

Une autre manière de faire est d’utiliser l’opérateur …​ (aussi appelé spread) pour éclater plusieurs tableaux et les rassembler dans un autre :

array/spread.js
link:./examples/array/spread.js[role=include]
  1. Affiche ["Athos", "Porthos", "Aramis", "d’Artagnan", "Albert"].

À l’inverse, la méthode join() concatène tous les éléments dans une chaîne de caractères avec le séparateur de notre choix (optionnel).

array/join.js
link:./examples/array/join.js[role=include]
  1. Affiche "ID,NOM,PRENOM" – le séparateur par défaut est une virgule.

  2. Affiche "ID;NOM;PRENOM" – on a choisi le point-virgule comme séparateur.

  3. Affiche "IDNOMPRENOM".

7.3. Itérer sur les valeurs avec des boucles

Les boucles sont une manière de parcourir plusieurs valeurs. Elles aident à mettre en place des automatismes pour éviter de répéter du code.

array/loop.js
link:./examples/array/loop.js[role=include]
  1. Affiche successivement chaque valeur du tableau – "lundi", "mardi", "mercredi", "jeudi", "vendredi".

Prenons le temps de revenir sur cet exemple. On y découvre plusieurs manières d’écrire des boucles sur un tableau :

for…​of

On affecte une variable avec chaque élément (opérateur of) du tableau. Les expressions situées entre accolade sont exécutées pour chaque élément du tableau.

forEach(element ⇒ expression)

La méthode forEach applique une fonction anonyme pour chaque élément du tableau.

Il y a en réalité deux manières d’itérer avec la boucle for : sur les index (avec l’opérateur in) et sur les valeurs (avec l’opérateur of).

array/for-of-in.js
link:./examples/array/for-of-in.js[role=include]
  1. Affiche successivement 0 puis 1.

  2. Affiche successivement "samedi" puis "dimanche" – l’index sert à retrouver la valeur dans le tableau.

  3. Affiche successivement "samedi" puis "dimanche".

La méthode forEach() propage en réalité trois arguments à notre fonction anonyme : l’élément en cours de l’itération, l’index de l’élément et le tableau d’origine.

Pourquoi passer le tableau d’origine alors qu’on itère dessus ? Pour donner du contexte au cas où on opère avec une fonction nommée. Nous verrons un usage concret de ce troisième argument dans la section “Transformer les valeurs”.

array/foreach-function.js
link:./examples/array/foreach-function.js[role=include]
  1. Applique la fonction printIndex() pour chaque élément du tableau undeux.

  2. Affiche successivement "un : index 0" puis "deux : index 1".

Outre l’inspection et l’affichage des valeurs, les boucles offrent la liberté de trier, de transformer les valeurs, de filtrer selon des conditions, mais aussi de créer de nouvelles structures de données.

Ces méthodes sont décrites dans les sections suivantes.

7.4. Trier les valeurs

La méthode sort() change l’ordre des éléments d’un tableau. Elle utilise une fonction anonyme qui compare deux éléments entre eux ; elle retourne un nombre positif, négatif ou égal à zéro selon la logique que l’on souhaite donner au tri :

  • Quand la comparaison est négative, sort() place le premier élément avant le second.

  • Quand la comparaison est positive, sort() place le premier élément après le second.

  • Quand la comparaison est égale à zéro, nulle ou non spécifiée, l’ordre des éléments reste inchangé.

array/sort.js
link:./examples/array/sort.js[role=include]
  1. Affiche [1, 2, 3].

  2. Affiche [ { label: "un", order: 1 }, { label: "deux", order: 2 } ] – le tableau a été trié sur la valeur de order.

Les chaînes de caractères peuvent être comparées avec localeCompare(). Cette méthode retourne un nombre après une comparaison caractère par caractère entre deux chaînes.

array/sort-strings.js
link:./examples/array/sort-strings.js[role=include]
  1. Affiche ["a", "A", "b", "c"] – les majuscules influencent le tri.

  2. Affiche [ { label: "deux", order: 2 }, { label: "un", order: 1 } ] – le tableau a été trié sur la valeur de label.

Tip
Alternative Array.reverse()

La méthode reverse() transforme le tableau d’origine en inversant l’ordre de ses éléments.

array/reverse.js
link:./examples/array/reverse.js[role=include]
  1. Affiche ["dimanche", "samedi"].

7.5. Transformer les valeurs

La méthode map() fonctionne quasiment comme forEach(), à ceci près qu’elle retourne un nouveau tableau, constitué des valeurs retournées par la fonction appliquée sur chaque élément.

array/map.js
link:./examples/array/map.js[role=include]
  1. Retourne ['A', 'B', 'C'] – on a passé tous les éléments en lettres majuscules.

Le troisième argument de la méthode map() prend ici tout son sens. Par exemple, si l’on souhaite dédoublonner un tableau :

array/map-dedupe.js
link:./examples/array/map-dedupe.js[role=include]
  1. Affiche [null, null, "un", "deux"].

Cet exemple vérifie, à chaque itération, si la valeur de l’élément est contenue dans la suite du tableau. array.slice(index+1) crée un nouveau tableau contenant tous les éléments situés après l’élément courant (index+1).

La méthode de transformation reduce() est différente, car elle passe le résultat de la précédente itération à la suivante. C’est comme si elle accumulait les résultats. Elle retourne une valeur finale qui peut être autre chose qu’un tableau.

array/reduce.js
link:./examples/array/reduce.js[role=include]
  1. Effectue une réduction à l’aide de la fonction sum() et d’une valeur par défaut de 0 – affiche 22 à l’issue des itérations .

  2. La valeur de l’élément est le second paramètre ; le premier paramètre correspond au résultat de l’itération précédente ou à la valeur initiale, passée en argument à reduce().

7.6. Filtrer les valeurs

La méthode filter() retourne un nouveau tableau filtré à l’aide d’une fonction anonyme. Seuls les éléments qui satisfont à la condition établie par la fonction se retrouvent dans le nouveau tableau.

array/filter.js
link:./examples/array/filter.js[role=include]
  1. Retourne [3] – c’est la seule valeur qui soit un nombre.

  2. Retourne ["un", "deux", 3] – ce sont les valeurs non nulles.

7.7. Identifier des valeurs

Les méthodes indexOf(), lastIndexOf() et includes() identifient une valeur exacte au sein d’un tableau.

indexOf() et lastIndexOf() retournent l’index de la valeur recherchée. Si aucun élément n’a été retrouvé, elles retourneront la valeur -1.
includes() retourne un booléen indiquant si la recherche est fructueuse (true) ou non (false).

array/index-of-includes.js
link:./examples/array/index-of-includes.js[role=include]
  1. Affiche 0 – le premier "un" est l’élément ``0` du tableau.

  2. Affiche 1 – le premier "deux" est l’élément ``1` du tableau.

  3. Affiche -1 – cet élément est absent du tableau.

  4. Affiche 3 – le dernier "deux" est l’élément ``3` du tableau.

  5. Affiche true – l’élément "un" existe dans le tableau.

  6. Affiche false – l’élément "trois" n’existe pas dans le tableau.

Il existe ensuite d’autres méthodes comme find(), some() et every(). Elles identifient des éléments à partir d’une fonction. Les conditions de recherche sont plus complètes, car on n’est pas obligé de connaître la valeur exacte recherchée.

La méthode find() retourne le premier élément qui remplisse la condition ; findIndex() en retourne l'index.

array/find.js
link:./examples/array/find.js[role=include]
  1. La fonction retourne true si la valeur passée en argument est un nombre supérieur à 50.

  2. Affiche 100.

  3. Affiche 3 – c’est l’index de la valeur 100.

Les méthodes some() et every() retournent true respectivement si au moins une itération est satisfaisante et si toutes les itérations sont satisfaisantes.

array/some.js
link:./examples/array/some.js[role=include]
  1. Affiche false – toutes les valeurs ne sont pas égales à undefined.

  2. Affiche true – au moins une valeur est égale à undefined.

  3. Affiche false – il n’y a plus de valeur undefined dans le tableau, car on a utilisé la méthode filter pour supprimer les valeurs vides.

7.8. Décomposition de tableau (destructuring)

L’affectation par décomposition (destructuring) est une manière élégante de piocher des valeurs dans un tableau. Ce mécanisme n’altère pas le contenu des variables décomposées et existe aussi pour les objets.

array/destructuring.js
link:./examples/array/destructuring.js[role=include]
  1. Affiche "lundi".

  2. Affiche "mardi".

  3. Affiche "mercredi" – l’utilisation des virgules sans variable a permis de sauter des positions dans la décomposition.

La décomposition se combine agréablement avec l’opérateur …​ (spread). Il accumule le reste des éléments dans une variable, sous forme de tableau.

array/destructuring-rest.js
link:./examples/array/destructuring-rest.js[role=include]
  1. Affiche ["mercredi", "jeudi", "vendredi"].

La méthode slice() offre davantage de souplesse pour gérer les limites. On choisit l’index de début (inclus) et celui de fin (non inclus) de la décomposition.

array/slice.js
link:./examples/array/slice.js[role=include]
  1. Affiche ["deux", "trois", "quatre"] – à partir de l’index 1.

  2. Affiche ["deux"] – à partir de l’index 1 et jusqu’à l’index 2 (non inclus).

Si les valeurs de début et/ou de fin sont négatives, les index sont calculés à partir de la fin du tableau.

array/slice-negative.js
link:./examples/array/slice-negative.js[role=include]
  1. Affiche ["quatre"] – premier élément à partir de la fin.

  2. Affiche ["deux", "trois", "quatre"] – les trois premiers éléments à partir de la fin.

  3. Affiche ["un", "deux", "trois"] – jusqu’au dernier élément à partir de la fin (non inclus).

  4. Affiche ["un"] – jusqu’au troisième élément à partir de la fin (non inclus).

8. Représenter des structures d’objet et y accéder

Les structures d’objet servent à lister des éléments de tout type au sein d’une même variable. L’indexation se fait comme dans un dictionnaire, avec un identifiant unique pour chaque valeur.

object/base.js
link:./examples/object/base.js[role=include]
  1. Affiche "Francine".

  2. On affecte une valeur numérique à l’index age une fois l’objet créé.

  3. Affiche 25 – la valeur numérique précédemment affectée.

  4. Affiche undefined – aucune valeur n’est affectée pour cette clé.

Une autre syntaxe existe pour créer des valeurs et y accéder en utilisant des variables en guise d’identifiant d’index.

object/dynamic.js
link:./examples/object/dynamic.js[role=include]
  1. Affecte la chaîne @FrancineDu26 dans l’index correspondant à la valeur de la variable SOCIAL_NETWORK.

  2. Affiche "@FrancineDu26".

8.1. Décomposition d’objet (destructuring)

L’affectation par décomposition (destructuring) est une manière élégante de piocher des valeurs dans un objet. Ce mécanisme existe aussi pour les tableaux.

object/destructuring.js
link:./examples/object/destructuring.js[role=include]
  1. Affiche "Drôme" – on a décomposé la clé location.

  2. Affiche "Francine" – on a décomposé puis renommé la clé first_name en une nouvelle variable : prenom.

  3. Affiche false – on a décomposé la clé is_admin et, comme elle n’existe pas, on a spécifié la valeur par défaut false, au lieu de undefined.

La décomposition se combine agréablement avec l’opérateur …​ (spread). Il accumule le reste des éléments dans une variable, sous forme d’objet.

object/destructuring-rest.js
link:./examples/object/destructuring-rest.js[role=include]
  1. Affiche "Francine".

  2. Affiche { location: "Drôme", twitter: "@FrancineDu26" }.

8.2. Combiner des objets

Object.assign() est une méthode qui sert à étendre et combiner plusieurs objets. On a le choix d’intégrer les nouveaux éléments à un objet existant ou bien d’en créer un nouveau. Les objets sont combinés dans le premier paramètre de la fonction.

object/assign.js
link:./examples/object/assign.js[role=include]
  1. Affiche { first_name: "Francine", location: "Drôme" } – la nouvelle variable contient nos deux objets combinés.

  2. Affiche { first_name: "Francine" } – ce sont les valeurs originelles de notre objet.

  3. Affiche { first_name: "Francine", location: 'Ardèche' } – l’objet o2 a reçu la nouvelle propriété location.

Notez que les affectations se font de gauche à droite. Toute clé existante est remplacée.

La décomposition d’objet sert également à combiner des objets entre eux.

object/destructuring-spread.js
link:./examples/object/destructuring-spread.js[role=include]
  1. Affiche { first_name: "Francine", location: "Drôme" }.

8.3. Itérer sur des objets

La méthode Object.entries() est probablement la plus adaptée pour itérer à la fois sur les clés et sur les valeurs d’un objet. Elle retourne un tableau qui contient autant de paires de [clé, valeur] qu’il y a d’éléments dans l’objet.

object/entries.js
link:./examples/object/entries.js[role=include]
  1. Affiche [[ "first_name", "Francine" ], [ "location", "Drôme" ]].

Nous sommes libres d'itérer sur les valeurs et d’utiliser la décomposition de tableaux pour rendre notre code explicite :

object/entries-loop.js
link:./examples/object/entries-loop.js[role=include]
  1. Affiche successivement "francine.first_name vaut Francine" puis "francine.location vaut Drôme".

Deux autres méthodes récupèrent soit la liste des clés d’un objet (Object.keys()) soit la liste de ses valeurs (Object.values()). Dans les deux cas, les résultats sont retournés sous forme d’un tableau.

object/keys.js
link:./examples/object/keys.js[role=include]
  1. Affiche ["first_name", "location"].

  2. Affiche ["Francine", "Drôme"].

8.4. Identifier des valeurs

Il y a trois manières d’identifier si un objet contient une valeur associée à une clé.

Le plus simple est d’utiliser la méthode hasOwnProperty(). Elle prend en argument le nom de la clé à tester et retourne un booléen.

object/has-own-property.js
link:./examples/object/has-own-property.js[role=include]
  1. Affiche true.

  2. Affiche false – cette clé n’existe pas dans cet objet.

La seconde manière est d’utiliser l’opérateur in. On l’aura déjà rencontré lors des boucles ; ici, on l’utilise une seule fois.

object/key-in.js
link:./examples/object/key-in.js[role=include]
  1. Affiche true.

  2. Affiche false – cette clé n’existe pas dans cet objet.

Enfin, on peut tester la valeur associée avec la syntaxe standard objet.clé.

object/key.js
link:./examples/object/key.js[role=include]
  1. Affiche true.

  2. Affiche false.

Attention toutefois : cette méthode teste uniquement la valeur. Si la clé existe et contient undefined, vous ne verrez pas la différence.

object/key-undefined.js
link:./examples/object/key-undefined.js[role=include]
  1. Affiche false – la valeur undefined est convertie en false.

  2. Affiche false – la clé existe bien, mais elle contient la valeur undefined.

  3. Affiche true – le test se fait sur l’existence de la clé.

  4. Affiche true – idem.

9. Lire et écrire des données au format JSON

JSON (http://json.org) est un format de données textuel standardisé. Son but est de représenter des données informatiques de manière interopérable entre différents langages.

json/base.json
link:./examples/json/base.json[role=include]

Le format JSON ressemble beaucoup à une structure d'objet ECMAScript. La représentation est plus stricte car toute donnée doit être représentée de manière textuelle. Ainsi, toutes les clés sont entourées de guillemets doubles.

Les types de données autorisés sont les nombres, les chaînes de caractères, les booléens, les tableaux, les objets et la valeur null. On ne peut donc pas représenter de fonction, d'instance d’objet ni même la valeur undefined.

ECMAScript embarque le nécessaire pour parser depuis et convertir en JSON. Cela se fait respectivement avec les fonctions JSON.parse() et JSON.stringify().

La fonction JSON.parse() consomme du texte. Elle retourne une représentation ECMAScript ou lance une erreur en cas de problème.

json/parse.js
link:./examples/json/parse.js[role=include]
  1. Affiche "Hello World!".

  2. Affiche 32.

  3. Affiche {price_tag: 32, title: "Node.js"}.

À l’inverse, la fonction JSON.stringify() convertit une structure ECMAScript en chaîne de caractères au format JSON :

json/stringify.js
link:./examples/json/stringify.js[role=include]
  1. Affiche "{\"lat\":48.8503439,\"lon\":2.34658949}".

La fonction JSON.stringify() parcourt tous les éléments pour les sérialiser en forme textuelle. Quand elle rencontre la la clé spéciale toJSON(), elle l’utilise pour effectuer la conversion :

json/to-json.js
link:./examples/json/to-json.js[role=include]
  1. Affiche "\"geo=48.8503439,2.34658949\"" – c’est la sérialisation définie par notre fonction toJSON.

  2. Affiche "{\"lat\":48.8503439,\"lon\":2.34658949}" – sans la clé toJSON, notre objet initial est sérialisé tel quel.

Notre implémentation contenue dans la fonction toJSON() est responsable de renvoyer du texte seulement et de choisir les clés à sérialiser.

json/to-json-extra.js
link:./examples/json/to-json-extra.js[role=include]
  1. Affiche "\"geo=48.8503439,2.34658949\"".

Dans cette variante d’exemple, la clé city n’a pas été sérialisée car notre fonction toJSON() se préoccupait seulement des clés lat et `lon`.

mdn::global[JSON]

10. Interagir avec des dates

Les calculs de date s’effectuent à l’aide des objets Date. Chaque instance représente un moment dans le temps, à un jour et à une heure donnée.

date/base.js
link:./examples/date/base.js[role=include]
  1. On initialise l’objet date past au 04 décembre 2013.

  2. Affiche 2013 – l’année liée à l’objet past.

  3. Affiche 2024 – l’année liée à l’objet now (date du jour).

Un certain nombre de méthodes retournent différents éléments de la date contenue dans l’objet : année, secondes, jour de la semaine, etc. Il en existe tout autant pour modifier ces éléments de date.

date/set.js
link:./examples/date/set.js[role=include]
  1. Change la date vers l’année 2015.

  2. Affiche "2015-12-04T10:00:00.000Z".

  3. Change la date vers le mois 1.

  4. Affiche "2015-02-04T10:00:00.000Z" – pourquoi le mois de février ??

L’exemple précédent illustre l’ambiguïté de la notion de mois. Il s’agit en réalité de l'index du mois : 0 correspond à janvier, 1 à février, etc.

Les méthodes natives font pour la plupart référence à l’anglais. Elles offrent peu de confort de manipulation – on aimerait pouvoir compter facilement le nombre de jours entre deux dates, ou retirer 30 jours.

Quand nous utiliserons Node et npm, nous verrons que nous aurons à disposition des bibliothèques facilitant les manipulations de dates.

mdn::global[Date]

10.1. Formatage internationalisé (Intl.DateTimeFormat)

La spécification ECMA Intl a été conçue pour ajouter des fonctionnalités relatives aux langues. Cette spécification est complémentaire. Son comportement varie en fonction du système d’exploitation – mode d’installation de Node et/ou version du navigateur web.

Les méthodes toLocaleString(), toLocaleDateString() et toLocaleTimeString() renvoient respectivement une version localisée d’une date complète, d’une date et d’une heure.

date/to-locale-date.js
link:./examples/date/to-locale-date.js[role=include]
  1. Affiche 04/12/2013.

  2. Affiche décembre.

Caution
Attention M01, M02, etc. ?

Si, en formatant une date, les caractères M01, M02 ou autre s’affichent, c’est que le système n’est pas configuré avec les libellés de la langue demandée.

La langue par défaut est l’anglais.

mdn::global[Date/toLocaleDateString]

Une version plus verbeuse consiste à créer un formateur avec Intl.DateTimeFormat. Ce formateur se réutilise pour transformer plusieurs fois des dates différentes avec les mêmes réglages ou une même date avec des formatages différents.

date/intl.js
link:./examples/date/intl.js[role=include]
  1. Affiche 4 déc. 2013.

  2. Affiche mercredi 4 décembre 2013.

mdn::global[DateTimeFormat]

11. Partager une logique avec des objets de même nature (Class)

Une classe est une structure qui partage des propriétés et des méthodes entre les objets qui y font appel. Une instance de classe est créée en préfixant un appel de fonction par l’opérateur `new`.

const date1 = new Date();
const date2 = new Date('2013-12-04');

Nos deux variables sont des objets issus de la classe Date. Chacune des variables bénéficie des méthodes définies par cette classe.

Autrement dit, si les structures d’objet définissent des données, les classes définissent des comportements partagés.

class/base.js
link:./examples/class/base.js[role=include]
  1. Le constructeur reçoit un ou plusieurs argument(s) lors de l’instanciation de la classe.

  2. this fait référence à ce contexte, c’est-à-dire à cette instance de classe ; deux instances peuvent être initialisées avec des données différentes.

  3. toJSON() est une méthode de la classe.

  4. isbn() est un accesseur (préfixe get) – une propriété dont la valeur est calculée à chaque fois qu’elle est appelée.

  5. clean() est une méthode dite statique – elle est appelée en dehors d’une instance.

Nous développerons cet exemple dans les sections qui suivent. On peut d’ores et déjà noter que la structure d’une classe se décompose en plusieurs parties :

La définition

Définit le nom de la classe que l’on pourra instancier.

Le constructeur

Partie exécutée lorsque la classe est instanciée. On y met le moins de choses possibles. En général, on copie les données passées en argument.

Les méthodes

Fonctions partagées entre toutes les instances de la classe.

Les méthodes statiques

Fonctions partagées sans avoir à instancier la classe.

Les accesseurs et mutateurs

Fonctions qui définissent le comportement de propriétés dynamiques.

Le contexte (this)

On peut s’y référer dans les méthodes de la classe pour dire je fais référence à cet objet et, donc, appeler les données et méthodes attenantes.

mdn::reference[Classes, title="Classes", text="les classes"]

11.1. Méthodes d’instance

Les méthodes définissent des comportements partagés entre chaque instance de la classe. Elles servent à retourner ou transformer des valeurs rattachées à l’objet.

class/methods.js
link:./examples/class/methods.js[role=include]
  1. Affiche false – la propriété n’existe pas.

  2. Affiche true – la propriété is_published a été changée à la ligne précédente.

  3. Affiche false – les données sont étanches entre chaque instance.

11.2. Méthodes statiques

Les méthodes statiques sont pratiques pour mettre à disposition du code métier de manière organisée. Elles se caractérisent par le mot-clé static devant un nom de fonction.

class/static.js
link:./examples/class/static.js[role=include]
  1. On appelle la méthode statique Book.clean() pour nettoyer le code EAN13.

  2. Affiche "9782212139938" – la valeur a bien été nettoyée.

  3. Affiche undefined – les méthodes statiques ne sont pas accessibles depuis l’instance de classe.

On verra dans le chapitre sur Node qu’on peut se baser sur les modules pour partager du code sans avoir à l’affecter à une classe.

mdn::reference[Classes/static, title="Méthodes statiques", text="les méthodes statiques"]

11.3. Accesseurs et mutateurs

Ce type de méthode définit des attributs dont la lecture ou l’écriture sont dynamiques.

L’accesseur est une fonction préfixée par le mot-clé get ; elle retourne la valeur d’un attribut.

getters.js
link:./examples/getters.js[role=include]
  1. Définition de l’accesseur isbn().

  2. Affiche "9782212139938" – c’est une propriété de l’objet nodebook.

  3. Affiche "2212139938"isbn() s’utilise comme un attribut mais sa valeur est calculée à chaque fois qu’elle est appelée.

Le mutateur est une fonction préfixée par le mot-clé set ; elle définit la valeur d’un ou plusieurs attribut(s).

setters.js
link:./examples/setters.js[role=include]
  1. Définition du mutateur ean13() – il accepte un seul argument.

  2. Affiche 978 – l’attribut a été créé lors de l’appel du mutateur.

  3. Affiche 2212139938 – idem.

  4. Affiche undefined – il faudrait créer un accesseur get ean13() pour recomposer dynamiquement sa valeur.

mdn::reference[Functions/get, title="Accesseurs", text="les accesseurs"] mdn::reference[Functions/set, title="Mutateurs", text="les mutateurs"]

11.4. Héritage

L’héritage est un mécanisme d’extension de classe. C’est une pratique peu employée en JavaScript, principalement en raison de sa nature modulaire et fonctionnelle.

L’héritage se caractérise par l’usage du mot-clé extends lors de la définition de la classe et aussi par l’utilisation de l’opérateur super() dans le constructeur.

class/extends.js
link:./examples/class/extends.js[role=include]
  1. La classe Product affecte un titre par défaut lorsqu’un nouvel objet est initialisé.

  2. La classe Book affecte un titre donné en argument et, sinon, se base sur la valeur par défaut de la classe Product.

  3. Affiche 'Node.js'.

  4. Affiche 'Sans titre' – la propriété title ne se définit pas dans le constructeur (cf. class Product).

En pratique, c’est comme si on empilait les classes les unes sur les autres. On lègue des méthodes aux classes qui héritent. Si une méthode porte le même nom, la méthode “en haut de la pile” a la priorité.

L’appel à la fonction super() appelle le constructeur de la classe étendue. Si on ne l’appelle pas, le constructeur de la classe parente ne sera pas invoqué.

On reparlera de l’héritage dans le chapitre 9 avec un exemple populaire d’héritage appliqué aux composants visuels avec la bibliothèque React.

12. Coordonner des actions asynchrones (Promise)

Une promesse est un objet retourné immédiatement mais dont le résultat est obtenu plus tard, de manière asynchrone. Cette résolution est soit positive soit négative.

promise/base.js
link:./examples/promise/base.js[role=include]
  1. Affiche Promise – ce n’est pas le résultat que l’on voit, mais l’objet avec lequel interagir pour être prévenu de la mise à disposition du résultat.

  2. Affiche "un".

  3. Affiche "deux" – c’est parce que la ligne d’avant a mis en attente la fonction anonyme.

  4. Affiche "promesse tenue" en dernier.

Note
Design Pattern Executor

Le fait qu’une fonction nous passe d’autres fonctions pour commander un résultat s’appelle le pattern Executor.

Une Promise s’orchestre en deux temps :

  • L’initialisation
    On décide de la manière dont le traitement asynchrone sera effectué.

  • La résolution
    Positive en appelant resolve() ou négative, en appelant reject(). Le résultat passé à resolve() sera transmis au premier argument de then(). Le résultat passé à reject() sera transmis au deuxième argument de then(), mais aussi au premier argument de catch().

Une instance de Promise expose plusieurs méthodes pour propager le statut de son exécution :

then(onSuccess[, onError])

Fonction acceptant un callback de résolution et un autre de rejet (facultatif).

catch(onError)

Fonction acceptant un callback de rejet.

promise/then-catch.js
link:./examples/promise/then-catch.js[role=include]
  1. La fonction oddTime() accepte un argument de type <<date,Date>. Elle résout la promesse positivement (resolve()) si le nombre de secondes est impair et négativement (reject) sinon.

  2. Utilisation de la forme compacte de then() avec deux callbacks : un de succès (associé à resolve()) et un d’échec (associé à reject()).

  3. On crée une nouvelle promesse, avec une date calée une seconde plus tard.

  4. Affiche "le nombre de secondes est impair :-)" puisque la résolution est positive.

  5. Affiche "le nombre de secondes n’est pas impair :-(" puisque la résolution est négative.

Note
Histoire Standard Promise/A+

Historiquement, de nombreuses bibliothèques ont proposé leur propre implémentation de promesses. Elles avaient le défaut de ne pas être interopérables. La spécification Promise/A+ (https://github.com/promises-aplus/promises-spec) a émergé pour établir un standard de compatibilité.

ECMAScript 2015 introduit nativement cette API. Il n’y a donc plus besoin de polyfill ou de bibliothèque pour en bénéficier.

En général, on utilise les promesses pour aller plus vite, parce qu’on peut continuer à traiter d’autres actions en attendant l’arrivée du résultat.

C’est comme quand on se rend au restaurant : les personnes en cuisine traitent des commandes (actions longues) tandis que les personnes au service gèrent des interactions plus courtes mais plus fréquentes. Au final, le ticket de commande contient la liste des promesses dont on attend la résolution.

Nous verrons d’autres utilisations des promesses dans le reste de l’ouvrage, plus particulièrement avec fetch() au chapitre 9 ainsi qu’avec promisify au chapitre 4.

mdn::global[Promise, text="les promesses"]

Tip
Lien Guide des promesses

Le guide https://www.w3.org/2001/tag/doc/promises-guide est très complet. Il est en anglais ainsi qu’en libre consultation sur le site du W3C.

Son dépôt GitHub https://github.com/w3ctag/promises-guide permet d’y contribuer.

12.1. Collection de promesses

Promise.all() est une méthode statique de la class Promise. Elle accepte un tableau de promesses et en retourne elle-même une promesse. Cette dernière est résolue positivement si toutes les promesses réussissent et négativement dès que l'une d’entre elles échoue.

promise/all.js
link:./examples/promise/all.js[role=include]
  1. Cette fonction résout la promesse après un délai aléatoire compris entre 0 et 2000 millisecondes.

  2. On passe trois promesses à Promise.all().

  3. La résolution est déclenchée dès que les trois promesses sont résolues – l’argument contient un tableau listant les résultats dans l’ordre initial des promesses.

L’exemple précédent illustre la parallélisation des actions. Si la promesse la plus longue est résolue en une seconde, alors le temps d’attente pour la résolution de toutes les promesses est de une seconde.
Si on avait été dans un enchaînement séquentiel, le temps d’attente final aurait été l’accumulation des temps d’attente de la résolution de chacune des promesses.

Les promesses sont un des meilleurs moyens à notre disposition pour modulariser, linéariser et clarifier le sens du flot de notre code.

12.2. async/await

Les opérateurs async et await aident à mettre en pause l’interpréteur ECMAScript, en attendant le résultat d’une fonction asynchrone (préfixée par async). Les promesses sont implicitement compatibles. On peut donc les mettre à plat pour obtenir un résultat sans avoir à utiliser then() ni `catch()`.

Transformons l’exemple de la section précédente pour comprendre l’impact de async et de `await`.

promise/async-await.js
link:./examples/promise/async-await.js[role=include]
  1. On crée une fonction asynchrone auto-invoquée – parce qu’on ne peut pas encore utiliser de fonction asynchrone directement au niveau principal d’un script.

  2. Chaque utilisation de await met l’interpréteur en pause.

  3. L’affichage du temps d’exécution de chaque promesse se fait lorsque les trois promesses sont résolues.

On gagne en lisibilité, mais on perd en vitesse. Les promesses sont exécutées séquentiellement et non en parallèle. Il est important d’arbitrer les choix de conception et d’éviter de bloquer l’exécution de vos scripts sans raison explicite.

13. Conclusion

ECMAScript est un langage bien plus riche, complet et élégant qu’il n’y paraît.

Ce chapitre nous a appris les différentes structures de langage communes à tous les environnements comprenant ECMAScript. Cela s’applique aussi bien à Node qu’aux navigateurs web.
Je vous invite à revenir à ce chapitre pour vous rafraîchir la mémoire, mais aussi pour jouer avec les exemples afin de confirmer votre compréhension du langage.

Dans le chapitre suivant, nous allons relier ces apprentissages avec Node – notre interpréteur et environnement d’exécution JavaScript.