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.
-
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.
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 :
link:./examples/definition.js[role=include]
-
Code JavaScript standard.
-
Code JavaScript pour manipuler la DOM API (documents web).
-
Code JavaScript pour manipuler des Web API (fonctionnalités des navigateurs web).
-
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 :
-
des documents web représentés par le Document Object Model (DOM) ;
-
des navigateurs web au travers des Web API ;
-
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.
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.
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.
Table de compatibilité | |
Spécification |
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) | |
Table de compatibilité (Node.js) | |
Spécification |
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.
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.
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.
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.
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.
link:./examples/base/null.js[role=include]
On utilise null
pour signifier l'absence de valeur.
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.
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.
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.
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.
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.
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
:
link:./examples/variables/const.js[role=include]
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.
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.
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
.
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.
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
.
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.
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é.
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.
link:./examples/string/base.js[role=include]
-
Utilisation de guillemets simples (
\n
permet de revenir à la ligne). -
Utilisation de guillemets doubles — évite d’échapper le guillemet simple.
-
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.
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
.
link:./examples/string/length.js[role=include]
-
Affiche
14
. -
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 :
link:./examples/string/char-at.js[role=include]
-
Affiche
N
. -
Affiche
o
.
Deux autres fonctions transforment un texte en lettres minuscules et majuscules :
link:./examples/string/lower-upper-case.js[role=include]
-
Affiche
node.js
. -
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 :
link:./examples/string/trim-pad.js[role=include]
-
Affiche
Node.js
. -
Affiche
Node.js
. -
Affiche
Node.js
. -
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 :
link:./examples/string/index-of.js[role=include]
-
Retourne
4
. -
Retourne
-1
— aucune occurrence n’a été trouvée. -
Retourne
5
— première occurrence de la lettrea
. -
Retourne
7
— dernière occurrence de la lettrea
.
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 :
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 |
Illustrons leur utilisation en identifiant du texte répondant (match
)
à une expression régulière (/…/
) :
link:./examples/regexp/flags.js[role=include]
-
identifie et affiche
Node
, l’occurrence contenue dans le motNode.js
. -
affiche 2 fois
ode
— les occurrences contenues dans le motNode.js
etAnode
. -
affiche
Node
etnode
en combinant les deux optionsi
etg
— les occurrences contenues dans le motNode.js
etAnode
.
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*
.
link:./examples/regexp/syntax.js[role=include]
-
Affiche
["75"]
— les 2 premiers caractères numériques de la chaîne. -
Affiche
["75015"]
– les 5 premiers caractères numériques (satisfait la condition5
de entre 2 et 5 de{2,5}
). -
Affiche
["75015 Paris"]
. -
Affiche
["75015 Paris"]
— l’optioni
nous permet de nous passer de l’ensembleA-Z
. -
Affiche
["92410 Ville"]
— capture les caractères jusqu’à ce que la condition ne soit plus remplie en rencontrant le trait d’union (-
). -
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" …]
.
link:./examples/regexp/limits.js[role=include]
-
Affiche
["75015 Paris"]
. -
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. -
Affiche
["33900", "33900"]
– l’option multi-ligne itère de ligne en ligne jusqu’à trouver un motif. -
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.
link:./examples/regexp/classes.js[role=include]
-
Affiche
["♥ RegExp", "RegExp"]
— et s’arrête là car l’espace suivant n’est pas un caractère de mot. -
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 :
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 :
link:./examples/regexp/replace.js[role=include]
-
Affiche
"I love JavaScript"
— si le premier argument dereplace
est une chaîne, elle est convertie automatiquement en expression régulière. -
Affiche
"I ♥ PHP"
. -
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 :
link:./examples/regexp/replace-function.js[role=include]
-
Affiche
"I ♥ JAVASCRIPT"
— le dernier mot est transformé en majuscules.
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.
link:./examples/boolean/base.js[role=include]
-
Affiche
3
. -
Affiche
true
— la condition est vérifiée (3 équivaut strictement à 3). -
Affiche
true
— c’est la valeur de la variablecheck
suite à son assignation lors de la ligne précédente. -
Affiche
true
— la variablecheck
é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 ( |
link:./examples/boolean/convert.js[role=include]
-
Affiche
false
— il s’agit d’une chaîne vide. -
Affiche
true
— il s’agit d’une valeur non-nulle. -
Affiche
true
— le tableau est vide mais l’objet en lui-même vaut quelque chose : un tableau.
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.
link:./examples/number/base.js[role=include]
-
Affiche
true
— les deux valeurs sont strictement équivalentes. -
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 :
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
:
link:./examples/number/hexa.js[role=include]
-
Affiche
0
. -
Affiche
10
— carA
en hexadécimal vaut 10 en décimal. -
Affiche
160
— pour10 x 16
(une dizaine vaut 16). -
Affiche
2560
— pour10 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. Pour en savoir plus : https://fr.wikipedia.org/wiki/IEEE_754. |
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) |
|
link:./examples/number/operations.js[role=include]
-
Affiche
6
. -
Affiche
-2
. -
Affiche
8
. -
Affiche
0.5
. -
Affiche
2
. -
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]
Rendez-vous à la section suivante pour se prémunir des nombres qui n’en sont pas. |
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").
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.
link:./examples/number/is-nan.js[role=include]
Warning
|
Assertion
NaN n’est pas un nombre ?Il faut se méfier de 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 à number/is-not-a-nan.js
link:./examples/number/is-not-a-nan.js[role=include] |
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. |
link:./examples/number/parse.js[role=include]
-
Affiche
3
. -
Affiche
3.141592653589793
. -
Affiche
14
— ça ne change rien pourparseInt
. -
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 :
link:./examples/number/parse-int.js[role=include]
-
Affiche
16
. -
Affiche
10
—A
étant juste après10
en hexadécimal. -
Affiche
2560
— aurait pu s’écrire0xF00
— cf. le début de cette même section, à propos des bases.
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
link:./examples/number/round.js[role=include]
-
Affiche
3
— l’entier le plus proche est 3. -
Affiche
4
— l’entier le plus proche est 4. -
Affiche
4
— idem. -
Affiche
4
. -
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
:
link:./examples/number/to-fixed.js[role=include]
-
Affiche
'10.01'
. -
Affiche
'10'
.
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 :
-
la création de la fonction ;
-
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.
link:./examples/functions/base.js[role=include]
-
On crée la fonction
hello
. -
Affiche
[Function: hello]
— il s’agit de la définition de la fonction. -
Affiche
"Hello World"
— il s’agit de l'exécution de la fonction, qui retourne un résultat. -
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 valeurundefined
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. |
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.
link:./examples/functions/anonymous.js[role=include]
-
Affiche
"Deux secondes plus tard"
deux secondes après le début du script. -
Affiche
"Le processus se termine"
quand le processus se termine, une fois que toutes les actions en attente ont été exécutées.
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.
link:./examples/functions/callback.js[role=include]
-
Le troisième argument (et les suivants) de
setTimeout
sont transmis en paramètre de la fonction de callback. -
Cette fonction sera invoquée une seconde après le début du script, et recevra la date du moment en paramètre.
-
Affiche l’année de la date passée en argument — l’année en cours dans cet exemple.
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).
link:./examples/functions/rest.js[role=include]
-
Affiche
"On a compté 3 patates."
.
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.
link:./examples/array/base.js[role=include]
-
Affiche
["lundi", "mardi", "mercredi", "jeudi", "vendredi"]
. -
Affiche
5
— soit la longueur du tableau. -
Affiche
"mardi"
— c’est l’élément défini à l’index1
. -
Affiche
undefined
— il n’y a aucun élément défini à l’index5
.
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.
La fonction Array.from
est une manière de créer
un tableau à partir de quelque chose qui ressemble à un tableau.
link:./examples/array/from.js[role=include]
-
Affiche
["f", "r", "o", "m", "a", "g", "e"]
— chaque lettre de la chaîne. -
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]
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
:
link:./examples/array/concat.js[role=include]
-
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 array/spread.js
link:./examples/array/spread.js[role=include]
|
À 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.
link:./examples/array/join.js[role=include]
-
Affiche
"ID,NOM,PRENOM"
— le séparateur par défaut est une virgule (,
). -
Affiche
"ID;NOM;PRENOM"
— on a choisi le séparateur;
. -
Affiche
"IDNOMPRENOM"
.
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.
link:./examples/array/loop.js[role=include]
-
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
).
link:./examples/array/for-of-in.js[role=include]
-
Affiche successivement
0
puis1
. -
Affiche successivement
"samedi"
puis"dimanche"
— l’index permet de retrouver la valeur du tableau. -
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.
link:./examples/array/foreach-function.js[role=include]
-
Applique la fonction
printIndex
pour chaque élément du tableauundeux
. -
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.
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é.
link:./examples/array/sort.js[role=include]
-
Affiche
[1, 2, 3]
. -
Affiche
[ { label: "un", order: 1 }, { label: "deux", order: 2 } ]
— le tableau a été trié sur la valeur deorder
.
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 :
link:./examples/array/sort-strings.js[role=include]
-
Affiche
["a", "A", "b", "c"]
— les majuscules influencent le tri. -
Affiche
[ { label: "deux", order: 2 }, { label: "un", order: 1 } ]
— le tableau a été trié sur la valeur delabel
cette fois-ci.
Tip
|
Alternative
Array.reverse La méthode array/reverse.js
link:./examples/array/reverse.js[role=include]
|
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.
link:./examples/array/map.js[role=include]
-
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 :
link:./examples/array/map-dedupe.js[role=include]
-
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.
link:./examples/array/reduce.js[role=include]
-
Effectue une réduction à l’aide de la fonction
sum
et d’une valeur par défaut de0
— affiche22
à l’issue des itérations . -
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
.
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.
link:./examples/array/filter.js[role=include]
-
Retourne
[3]
— c’est la seule valeur qui est un nombre. -
Retourne
["un", "deux", 3]
— ce sont les valeurs non-nulles.
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
).
link:./examples/array/index-of-includes.js[role=include]
-
Affiche
0
— le premier"un"
est l’élément0
du tableau. -
Affiche
1
— le premier"deux"
est l’élément1
du tableau. -
Affiche
-1
— cet élément est absent du tableau. -
Affiche
3
— le dernier"deux"
est l’élément3
du tableau. -
Affiche
true
— l’élément"un"
existe dans le tableau. -
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.
link:./examples/array/find.js[role=include]
-
La fonction
isFinite
retournetrue
si la valeur passée en argument est un nombre supérieur à50
. -
Affiche
100
. -
Affiche
3
— c’est l’index de la valeur100
.
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.
link:./examples/array/some.js[role=include]
-
Affiche
false
— toutes les valeurs ne sont pas égales àundefined
. -
Affiche
true
— au moins une valeur est égale àundefined
. -
Affiche
false
— il n’y a plus de valeurundefined
dans le tableau, car on a utilisé la méthode filter pour supprimer les valeurs non-vides.
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.
link:./examples/array/destructuring.js[role=include]
-
Affiche
"lundi"
. -
Affiche
"mardi"
. -
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.
link:./examples/array/destructuring-rest.js[role=include]
-
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.
link:./examples/array/slice.js[role=include]
-
Affiche
["deux", "trois", "quatre"]
— à partir de l’index1
. -
Affiche
["deux"]
— à partir de l’index1
et jusqu’à l’index2
(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.
link:./examples/array/slice-negative.js[role=include]
-
Affiche
["quatre"]
— premier élément à partir de la fin. -
Affiche
["deux", "trois", "quatre"]
— les trois premiers éléments à partir de la fin. -
Affiche
["un", "deux", "trois"]
— jusqu’au dernier élément à partir de la fin (non-inclus). -
Affiche
["un"]
— jusqu’au troisième élément à partir de la fin (non-inclus).
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.
link:./examples/object/base.js[role=include]
-
Affiche
"Francine"
. -
On assigne une valeur numérique dans l’index
age
une fois l’objet créé. -
Affiche
25
— la valeur numérique précédemment assignée. -
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 :
link:./examples/object/dynamic.js[role=include]
-
Assigne la chaîne
@FrancineDu26
dans l’index correspondant à la valeur de la variableSOCIAL_NETWORK
. -
Affiche
"@FrancineDu26"
.
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.
link:./examples/object/destructuring.js[role=include]
-
Affiche
"Drôme"
— on a décomposé la clélocation
. -
Affiche
"Francine"
— on a décomposé puis renommé la cléfirst_name
en une nouvelle variable :prenom
. -
Affiche
false
— on a décomposé la cléis_admin
et comme elle n’existe pas, on a spécifié la valeur par défautfalse
, au lieu deundefined
.
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.
link:./examples/object/destructuring-rest.js[role=include]
-
Affiche
"Francine"
. -
Affiche
{ location: "Drôme", twitter: "@FrancineDu26" }
.
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.
link:./examples/object/assign.js[role=include]
-
Affiche
{ first_name: "Francine", location: "Drôme" }
— la nouvelle variable contient nos deux objets combinés. -
Affiche
{ first_name: "Francine" }
— ce sont les valeurs originelles de notre objet. -
Affiche
{ first_name: "Francine", location: 'Ardèche' }
— l’objetfrancine26
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.
link:./examples/object/destructuring-spread.js[role=include]
-
Affiche
{ first_name: "Francine", location: "Drôme" }
.
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.
link:./examples/object/entries.js[role=include]
-
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 :
link:./examples/object/entries-loop.js[role=include]
-
Affiche successivement
"francine.first_name vaut Francine"
puis"francine.location vaut Drôme"
. -
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.
link:./examples/object/keys.js[role=include]
-
Affiche
["first_name", "location"]
. -
Affiche
["Francine", "Drôme"]
.
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.
link:./examples/object/has-own-property.js[role=include]
-
Affiche
true
. -
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.
link:./examples/object/key-in.js[role=include]
-
Affiche
true
. -
Affiche
false
— cette clé n’existe pas dans cet objet.
Enfin, on peut tester la valeur associée avec la syntaxe standard objet.clé
.
link:./examples/object/key.js[role=include]
-
Affiche
true
. -
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.
link:./examples/object/key-undefined.js[role=include]
-
Affiche
false
— la valeurundefined
est convertie enfalse
. -
Affiche
false
— la clé existe bien, mais elle contient la valeurundefined
. -
Affiche
true
— le test se fait sur l’existence de la clé. -
Affiche
true
— idem.
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.
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.
link:./examples/json/parse.js[role=include]
-
Affiche
"Hello World!"
. -
Affiche
32
. -
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 :
link:./examples/json/stringify.js[role=include]
-
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 :
link:./examples/json/to-json.js[role=include]
-
Affiche
"\"geo=48.8503439,2.34658949\""
— c’est la sérialisation definie par notre fonctiontoJSON
. -
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.
link:./examples/json/to-json-extra.js[role=include]
-
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]
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.
link:./examples/date/base.js[role=include]
-
On initialise l’objet date
past
au 04 décembre 2013. -
Affiche
2013
— l’année liée à l’objetpast
. -
Affiche
2025
— l’année liée à l’objetnow
(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.
link:./examples/date/set.js[role=include]
-
Change la date vers l’année
2015
. -
Affiche
"2015-12-04T10:00:00.000Z"
. -
Change la date vers le mois
1
. -
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]
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.
link:./examples/date/to-locale-date.js[role=include]
-
Affiche
04/12/2013
. -
Affiche
décembre
.
Caution
|
Attention Tu vois M01, M02, etc. ?
Si en formatant une date, les caractères 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.
link:./examples/date/intl.js[role=include]
-
Affiche
4 déc. 2013
. -
Affiche
mercredi 4 décembre 2013
.
mdn::global[DateTimeFormat]
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.
link:./examples/class/base.js[role=include]
-
Le constructeur reçoit un ou plusieurs arguments lors de l’instanciation de la classe.
-
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. -
toJSON
est une méthode de la classe. -
isbn
est un accesseur (préfixeget
) — une propriété dont la valeur est calculée à chaque fois qu’elle est appelée. -
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]
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.
link:./examples/class/methods.js[role=include]
-
Affiche
false
— la propriété n’existe pas. -
Affiche
true
— la propriétéis_published
a été changée à la ligne précédente. -
Affiche
false
— les données sont étanches entre chaque instance.
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.
link:./examples/class/static.js[role=include]
-
On appelle la méthode statique
Book.clean
pour nettoyer le code EAN13. -
Affiche
"9782212139938"
— la valeur a bien été nettoyée. -
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]
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
.
link:./examples/class/getters.js[role=include]
-
Définition de l'accesseur
isbn
. -
Affiche
"9782212139938"
— c’est une propriété de l’objetnodebook
. -
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]
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.
link:./examples/class/extends.js[role=include]
-
Affiche
"Node.js"
— la propriététitle
est assignée dans le constructeur de la classeBook
. -
Affiche
"9782212139938"
— la propriétéean13
est assignée dans le constructeur deProduct
, la classe dont on hérite grâce àextends
et à l’appel desuper()
qui transmet les arguments d’initialisation. -
Affiche
undefined
— la propriététitle
n’est pas assignée dans le constructeur de la classeProduct
. -
Affiche
"9782212139938"
— la propriétéean13
est assignée dans le constructeur deProduct
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.
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.
link:./examples/promise/base.js[role=include]
-
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. -
Affiche
"un"
. -
Affiche
"deux"
— c’est parce que la ligne d’avant a mis en attente la fonction anonyme. -
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.
link:./examples/promise/then-catch.js[role=include]
-
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
). -
Utilisation de la forme compacte de
then
avec deux callbacks : un de succès (associé àresolve
) et un d’échec (associé àreject
). -
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.
-
Affiche
"le nombre de secondes est impair :-)"
si la résolution est positive. -
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. |
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.
link:./examples/promise/all.js[role=include]
-
Cette fonction résout la promesse après un délai aléatoire compris entre 0 et 2000 millisecondes.
-
On passe 3 promesses à
Promise.all
. -
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.
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
.
link:./examples/promise/async-await.js[role=include]
-
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.
-
Chaque utilisation de
await
met l’interpréteur en pause. -
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.
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.