Skip to content

Latest commit

 

History

History
2194 lines (1753 loc) · 76.3 KB

index.adoc

File metadata and controls

2194 lines (1753 loc) · 76.3 KB

Jouer avec JavaScript

Programmer une application Node c’est aussi l’occasion d’apprendre JavaScript. Ou de se mettre à jour ou de redécouvrir le langage.

Ce chapitre explique ce qu’il faut pour comprendre comment fonctionne JavaScript. Nous utiliserons une approche par l’exemple.

Sommaire
  • Qu’est-ce que JavaScript, pour de vrai ?

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

  • Manipuler 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 ?

Au lieu d’écrire plusieurs paragraphes expliquant ce qu’est et ce que n’est pas JavaScript, regardons ensemble un bloc de code contenant plusieurs expressions écrites en JavaScript :

definition.js
link:./examples/definition.js[role=include]
  1. Code JavaScript standard.

  2. Code JavaScript pour manipuler la DOM API (documents web).

  3. Code JavaScript pour manipuler des Web API (fonctionnalités des navigateurs web).

  4. Code JavaScript pour manipuler Node.js.

Ce que l’on peut comprendre de l’exemple de code ci-dessus, c’est que JavaScript permet de s’interfacer avec plusieurs choses :

  1. des documents web représentés par le Document Object Model (DOM) ;

  2. des navigateurs web au travers des Web API ;

  3. des systèmes informatiques avec Node.

ECMAScript correspond à l’ensemble des expressions du langage. Ces expressions sont étendues par des interfaces de programmation (API). Ces interfaces nous permettent de communiquer avec les documents web, les navigateurs web ou les systèmes informatiques.

Note
Histoire À propos de JavaScript

JavaScript a été inventé en 1995 par Brendan Eich alors qu’il était 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.

L’utilisation du terme JavaScript est resté dans le vocabulaire courant. Mais c’est bien d'ECMAScript dont on parle, vraiment.

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 en ECMAScript.

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 au côté serveur. Ce à quoi se réfère l’expression anglophone full stack.

javascript
Figure 1. Écosystème des technologies JavaScript.

Le langage ECMAScript — appelons-le ainsi à partir de maintenant — a évolué au fil du temps. Il s’est enrichi au fur et à mesure de nouvelles fonctionnalités, 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’un support natif 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

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 6 années de gestation.
Cette version a successivement été appelée ECMAScript Harmony, ECMAScript 6, puis ECMAScript 2015.

Le processus de standardisation a mis 6 années pour aboutir, mais cette fois-ci, les choses se sont déroulées différemment.
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 les compilateurs traceur de Google dès 2011 (https://github.com/google/traceur-compiler) puis le projet indépendant 6to5 dès 2014. 6to5 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 6 ans et l’implémentation par les différents implémenteurs pour profiter de ce qu’il y avait de meilleur.

Le prix à payer ? Un ticket d’entrée plus élevé lié à la maitrise 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. L’envie était de travailler fonctionnalité par fonctionnalité et de ne pas attendre trop longtemps avant de les ratifier. En conséquent, 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 ce dépôt GitHub : https://github.com/tc39/proposals. Le dernier stade avant la validation d’une fonctionnalité est le stage 3. Dès qu’une fonctionnalité passe en stage 4, elle est incluse dans la prochaine version d’ECMAScript — ECMAScript 2025 une fois l’année 2025 terminée.

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

2. Les éléments de base du langage

ECMAScript permet de manipuler différents types de données. Cette section s’intéresse à décrire les notions nécessaires pour s’approprier le reste des exemples de l’ouvrage. On apprendra notamment à manipuler des variables, à faire des boucles sur des collections et à faire la différence entre un objet et une fonction.

Mais qu’entend-t-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 peuvent être des guillemets simples ('), des guillemets doubles (") ou des guillemets 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 nombres flottants (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"]

Il existe trois autres types de données qui se basent sur ces types dits primitifs. Ces autres types 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 ([ et ]). Un tableau peut 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 de valeurs entre accolade ({ et }). Une clé désigne une valeur, qui peut être de n’importe quel type. Un objet fonctionne comme un dictionnaire : on associe une valeur à un intitulé, un label. L’ordre des valeurs 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. Une fonction peut être déclarée et ê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 sent que l’on doit écrire deux fois la même chose, on l’écrit dans une fonction et on l’appelle deux fois.

2.1. 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, combien vaut le prix du livre calculé dans l’exemple précédent ? Il suffirait 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 à qui on passe la valeur contenue dans la variable base_price.

Le mot-clé const nous a servi à déclarer des variables. On ne peut étiqueter une variable avec le même nom qu’une seule fois. L’exemple suivant générera une erreur si on déclare deux fois la même variable avec const :

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

2.2. Instructions

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

L’instruction if nous aide à exécuter du code qui remplit une condition. Cette condition peut être une valeur ou une expression. Cette expression est 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équent.

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 1 caractère. On expliquera ce comportement plus en détail dans la section manipuler des booléens.

L’instruction if peut être complétée avec l’instruction else pour exécuter du code qui répondrait au cas contraire. On peut 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 peut constater qu’on pourrait en emprunter un autre en modifiant la valeur des clés title et published.

2.3. 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. On peut y sentir une notion de frontière d’accès à la valeur d’une variable.

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

Ce que nous dit l’exemple précédent, c’est que la variable secret de type fonction a une portée globale au script en question. Qu’en-est-il de la variable mot encapsulée dans cette fonction ?

On pourrait le résumer ainsi : 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]

Dans le précédent exemple, 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.

L’utilisation de 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. Manipuler du texte

Il est commun d’avoir à manipuler des chaînes de caractères. Pour stocker des URL, des titres, des identifiants, des tweets voire des textes longs.

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

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

  3. Utilisation de guillemets obliques — autorise le multi-ligne.

On peut utiliser n’importe quel caractère : 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 chaine mais aussi pour la manipuler.

Par exemple, on peut connaître 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 peut aussi accéder à un caractère spécifique en utilisant la chaîne comme un tableau, ou en utilisant une méthode dédiée :

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

  2. Affiche o.

Deux autres fonctions transforment un texte en lettres minuscules et 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 le caractère 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. Si la valeur n’est pas trouvée, elle 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 les caractères / (slash), 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 ;

multi-ligne (m)

La recherche s’effectue sur toutes les lignes ;

global (g)

La recherche identifie tous les résultats — au lieu d’un seul et le premier ;

unicode (u)

À utiliser 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 2 fois ode — les occurrences contenues dans le mot Node.js et Anode.

  3. affiche Node et node en combinant les deux options i et g — les occurrences contenues dans le mot 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 - correspond à une plage de caractère.
Exemple [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 ensemble de caractères, exactement ({2} — exactement 2) au moins ({2,} — au moins 2) ou entre ({1,2} — entre 1 et 2 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 entre 2 et 5 de {2,5}).

  3. Affiche ["75015 Paris"].

  4. Affiche ["75015 Paris"] — l’option i nous permet de nous passer de 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 $)

Symbolise le début (^) ou la fin ($) d’une chaîne de caractères.
Quand l’option multi-ligne (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 l’un de l’autre. Exemple : /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. Ce tableau a la forme ["chaine 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 élément correspond au premier groupe de capture.

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

  4. Affiche ["33900", "33074", "33700"] — l’option multi-ligne globale retourne tous les groupes de capture.

On notera qu’il faut faire attention à ce que l’on regarde dans les résultats : le format de résultat varie selon qu’on utilise ou non des groupes de capture, selon qu’on utilise l’option globale ou multi-ligne.

Des classes de caractère servent de raccourcis pour désigner plusieurs caractères à la fois :

tout caractère (.)

Équivalent à tout caractère sauf le saut de ligne.

caractère de mot (\w)

Équivalent à tout caractère pouvant composer un mot anglais : les caractères accentués ne sont pas englobés par cette classe de caractères. Identique à [A-Za-z0-9_].

caractère numérique (\d)

Équivalent à tout caractère numérique. Identique à [0-9].

caractère d’espacement (\s)

Équivalent à 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 ♥ 2025" — 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"]

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 on peut les placer dans l’ordre de notre choix.

D’ailleurs 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. Manipuler des booléens

Un booléen est une donnée de type logique qui peut être vraie ou fausse en prenant respectivement la valeur true ou false. C’est un type de choix pour effectuer des assertions et vérifier des conditions.

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

  2. Affiche true — la condition est vérifiée (3 équivaut strictement à 3).

  3. Affiche true — c’est la valeur de la variable check suite à son assignation lors de la ligne précédente.

  4. Affiche true — la variable check équivaut strictement à 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, 0, NaN), 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. Manipuler des nombres (Number, Math)

ECMAScript ne fait pas de distinction entre des nombres 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ère.

Les nombres sont représentés par défaut en base 10. La plage de nombre utilisable démarre à moins l’infini et va jusqu’à plus l’infini. Des constantes définies par ECMAScript contiennent les valeurs minimales et maximales que l’on peut représenter dans un programme :

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

Il est aussi possible de compter dans d’autres bases comme la base 16, c’est-à-dire une représentation hexadécimale de 0 à 16, exprimée de 0 à F — A vaut 10 (car juste après 9), B vaut 11, etc. Leur notation se fait en préfixant la valeur par 0x :

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 x 16 (une dizaine vaut 16).

  4. Affiche 2560 — pour 10 x 16 x 16 (une centaine vaut 16x16).

Note
Rumeur JavaScript est nul en virgule flottante !

ECMAScript est souvent décrié par 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 :

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.

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 section ci-après).

  2. On nous a toujours interdit la division par zéro ? En vrai on crée l'infini.

  3. Seule cette opération retournera autre chose qu’un nombre.

Rendez-vous à la section suivante pour se prémunir des nombres qui n’en sont pas.

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

Certaines opérations n’aboutiront pas et n’afficheront pas d’erreur pour autant. Dans ce cas, le résultat de l’opération 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]
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 d’un point ECMAScript. Toute opération mathématique impliquant NaN renverra un NaN :

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

Il vaut mieux s’assurer qu’une variable est à la fois un nombre et n’est pas équivalent à NaN :

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

5.3. Convertir en nombre

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 avons deux fonctions à disposition :

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 étant juste après 10 en hexadécimal.

  3. Affiche 2560 — aurait pu s’écrire 0xF00 — cf. le début de cette même section, à propos des bases.

5.4. Formater et arrondir des nombres

Si l’envie vous prenait de vouloir arrondir des nombres, il existe quelques fonctions pour nous 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é.

Math.ceil, Math.floor

number/round.js
link:./examples/number/round.js[role=include]
  1. Affiche 3 — l’entier le plus proche est 3.

  2. Affiche 4 — l’entier le plus proche est 4.

  3. Affiche 4 — idem.

  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. Manipuler des fonctions

Une fonction est un mécanisme pour rendre du code réutilisable. Une fonction retourne un résultat. À l’aide de paramètres, on peut faire varier ce résultat.

Cela se passe en deux temps :

  1. la création de la fonction ;

  2. l'exécution de la fonction.

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ême, 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 3 parties :

les arguments

C’est la partie à gauche de la flèche (). Les arguments sont séparés par des virgules (exemple : (argument1, argument2, etc.)) ;

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. La valeur de retour est définie à l’aide du mot-clé return. La valeur undefined est retournée de manière implicite lorsque le mot-clé return est absent.

Tip
Rappelle-toi 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.

On en parle plus en détails dans la section portée en début de section.

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 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. La fonction peut recevoir 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ètre de la fonction de callback.

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

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

6.3. Paramètres du reste (rest parameters)

Les paramètres du reste collecte un nombre indéfini de paramètres. Les paramètres sont regroupés dans un même tableau (voir ci-après).

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

7. Manipuler des tableaux (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 attribué un numéro (index). On peut retrouver l’élément par son numéro, 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" — c’est l’élément défini à l’index 1.

  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 :

  1. la numérotation débute à l’index 0 ;

  2. la propriété length contient la longueur du tableau ;

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

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

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 ["Atos", "Portos", "Aramis", "d’Artagnan", "Albert"].

Cette méthode crée un nouveau tableau à partir d’une liste de tableaux passés en paramètre.

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 ["Atos", "Portos", "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. Ce séparateur est 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 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 la mise en place d’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 de faire des boucles sur un tableau :

for…​of

On assigne une variable pour 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 permet de retrouver la valeur du tableau.

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

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

Pourquoi passer le tableau d’origine alors qu’on itère sur ce même tableau ? 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 se trouve à l’index 0" puis "deux se trouve à l’index 1".

Outre l’inspection et l’affichage des valeurs, les boucles offrent la liberté de faire des tris, 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. Cette méthode utilise une fonction anonyme. La fonction anonyme 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 positive : sort placera le premier élément avant le second ;

  • quand la comparaison est négative : sort placera le premier élément après le second ;

  • quand la comparaison est nulle ou non spécifiée : l’ordre des éléments restera 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 la méthode 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 cette fois-ci.

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 la méthode 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"].

L’exemple précédent 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 reduce est une autre méthode de transformation. Elle 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. reduce 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 est un nombre.

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

7.7. Identifier une ou plusieurs 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 l'élément qui satisfait la condition en premier. La méthode findIndex retourne l'index de l’élément qui satisfait la condition en premier.

array/find.js
link:./examples/array/find.js[role=include]
  1. La fonction isFinite 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 un booléen lorsque la condition est satisfaite. some retourne true si au moins une itération est satisfaisante. every retourne true 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 non-vides.

7.8. Décomposition de tableau (destructuring)

L’affectation par décomposition (destructuring) est une manière élégante de piocher une ou plusieurs valeurs dans un tableau. La décomposition n’altère pas le contenu des variables décomposées. Ce mécanisme 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 …​ (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 et l’index de fin 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. Manipuler des structures d’objet

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 assigne une valeur numérique dans l’index age une fois l’objet créé.

  3. Affiche 25 — la valeur numérique précédemment assignée.

  4. Affiche undefined — aucune valeur n’est assignée pour cette clé.

Nous pouvons nous baser sur une autre syntaxe pour créer et accéder à des valeurs en utilisant des variables en guise d’identifiant d’index :

object/dynamic.js
link:./examples/object/dynamic.js[role=include]
  1. Assigne 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 une ou plusieurs 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 …​ (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 permet d’é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 francine26 a reçu la nouvelle propriété location.

À noter que les assignations se font de gauche à droite. Toute clé existante est remplacée.

La décomposition d’objet sert également à combiner un ou plusieurs 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 méthode la plus adaptée pour itérer à la fois sur les clés et sur les valeurs d’un objet. Object.entries est une fonction qui retourne un tableau. Ce tableau 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" ]].

Libre à nous 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".

  2. Idem.

Deux autres méthodes récupèrent soit la liste des clés d’un objet (Object.keys) soit la liste des valeurs d’un objet (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 une ou plusieurs 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. Sauf que cette fois, on l’utilise une seule fois — et non dans une boucle.

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, car 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. Manipuler des structures de données JSON

JSON (http://json.org) est un format de données textuel standardisé. Son but est de pouvoir 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ère, 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 la fonction de sérialisation rencontre la la clé spéciale toJSON, elle l’utilisera 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 definie 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. Manipuler des dates

Les calculs de date s’effectuent à l’aide des objets Date. Chaque instance de Date 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 2025 — l’année liée à l’objet now (aujourd’hui).

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.

La plupart des méthodes natives font 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 librairies 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 de date toLocaleString, toLocaleDateString et toLocaleTimeString renvoient une version localisée d’une date complète, d’une date et d’une heure, respectivement.

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 Tu vois M01, M02, etc. ?

Si en formatant une date, les caractères M01, M02 ou autre s’affiche, 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. Manipuler des classes d’objet (Class)

Une classe est une structure qui partage des propriétés et des méthodes entre objets qui l’instancient. 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');

Si on se réfère à l’exemple précédent, nos deux variables sont des objets issus de la classe Date. Chacune des variables bénéficie des méthodes définies par la classe Date.

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 arguments 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 y référer dans les méthodes de la classe pour dire je fais référence à cet objet. Et donc d’appeler les données et méthodes attenantes.

mdn::reference[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 préfixe 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]

11.3. Accesseurs et mutateurs

Ce type particulier de méthode permet de définir des attributs dont la valeur est dynamique (accesseur). Le mutateur gère l’affectation de valeur vers cet attribut dynamique. Ces méthodes sont préfixées par get ou set.

class/getters.js
link:./examples/class/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.

mdn::reference[Functions/get] mdn::reference[Functions/set]

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 de par sa nature modulaire et fonctionnelle.

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

class/extends.js
link:./examples/class/extends.js[role=include]
  1. Affiche "Node.js" — la propriété title est assignée dans le constructeur de la classe Book.

  2. Affiche "9782212139938" — la propriété ean13 est assignée dans le constructeur de Product, la classe dont on hérite grâce à extends et à l’appel de super() qui transmet les arguments d’initialisation.

  3. Affiche undefined — la propriété title n’est pas assignée dans le constructeur de la classe Product.

  4. Affiche "9782212139938" — la propriété ean13 est assignée dans le constructeur de Product qu’on appelle directement cette fois-ci.

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, elle sera écrasée.

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

On reparlera de l’héritage dans la section React du chapitre 9. C’est un exemple populaire d’héritage de composants graphiques.

12. Manipuler des promesses (Promise)

Une promesse est un objet retourné immédiatement. Le résultat est obtenu plus tard. Cette résolution peut être soit positive soit négative. On dit alors que l’action est asynchrone.

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é. Puis 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 callback 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, sinon elle résout la promesse négativement (reject).

  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 c’est histoire de s’assurer que les deux appels aboutissent à une résolution différente.

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

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

Note
Histoire Standard Promise/A+

Historiquement, de nombreuses librairies ont proposé leur propre implémentation de promesses. Elles avaient le défaut de ne pas être interopérables entre elles. La spécification Promise/A+ a émergé pour établir un standard de compatibilité.

ECMAScript 2015 introduit nativement cette API. Plus besoin de polyfill ou de librairie pour en bénéficier nativement.

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 effectuent le traitement 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 avec fetch() au chapitre 9, avec promisify au chapitre 4 et dans l'annexe B.

mdn::global[Promise]

Tip
Lien Guide des promesses

Un guide très complet — en anglais — est publié en libre consultation sur le site du W3C. Un dépôt sur GitHub 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. Promise.all retourne elle-même une promesse. Cette promesse est résolue positivement si toutes les promesses réussissent. Elle est résolue 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 3 promesses à Promise.all.

  3. La résolution est déclenchée dès que les 3 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 prend 1 seconde à être résolue, alors le temps d’attente pour la résolution de toutes les promesses est de 1 seconde.
Si on avait été dans un enchainement 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 Promise.all 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 — c’est pas pour frimer mais 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 3 promesses sont résolues.

On gagne en lisibilité, mais on perd en vitesse. Les promesses sont exécutées les unes à la suite des autres et non plus en parallèle. Il est important d’arbitrer les choix de design 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 parait.

Ce chapitre nous a appris les différentes structures de langage communes à toutes 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 — l’interpréteur et l’environnement d’exécution JavaScript que nous avons appris à installer au chapitre 2.