Javascript et NodeJS pour le développement web
NodeJS & npm
NodeJS est un environnement d’exécution Javascript, généralement utilisé côté serveur, basé sur le moteur Javascript V8 de Google.
Différences avec le JS du navigateur : - les variables globales prédéfinies sont différentes, par exemple document pour JS et process.env pour NodeJS - NodeJS peut directement accéder au système de fichiers (package fs), interdit pour JS - les objets Event sont légèrement différents
Installation sur tout le système
Debian
$ curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
$ sudo bash nodesource_setup.sh
$ sudo apt-get install -y nodejs
$ which node
/usr/bin/node
$ node
> 1 + 1 // expression à évaluer
2 // affichage de la valeur
> Ctrl-d # fermeture du flux stdin -> exitMacOsX
Utiliser l’installer graphique du site officiel
Installation locale par nvm
npm est un package-manager qui simplifie la distribution, le partage et l’installation de NodeJS.
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash # téléchargement du script d'install + exécution
$ nvm install 11 # demande à nvm d'installer la version 11.x de node la plus récente
$ nvm use 11 # pas nécessaire après une installation
$ which node
/home/chris/.nvm/versions/node/v8.10.0/bin/node # la version 8.10.0 est installée localement pour le user 'chris'Création d’un projet node
$ mkdir myproject ; cd myproject
$ npm init # création du fichier `package.json` qui définit les dépendances
... répondre aux questions ...Installation de packages npm
Installation globale d’un package
Installation locale (pour le projet courant) d’un package et sauvegarde de la dépendance dans ./package.json. Les fichiers sont copiés dans le répertoire ./node_modules/<package_name>
Installation de tous les packages référencés dans package.json :
NodeJS est un environnement d’exécution Javascript, généralement utilisé côté serveur, basé sur le moteur Javascript V8 de Google.
JSON
[TODO]
Héritage des objets par prototypes
Un objet est une instance du type de base Object :
$ node
> x = new Object()
{}
> x.a = 1 ; x
{ a: 1 }
Javascript est un langage objet qui utilise un héritage par prototype: lorsque obj.prop est évalué, la clé prop est recherchée d’abord dans obj, puis dans le type père obj.prototype (s’il existe), puis à défaut dans obj.prototype.prototype, etc. jusqu’à Object
Lors d’un appel à new Type(args) :
- un nouvel objet est créé par clonage de Type.prototype. Il possèdera donc déjà toutes les propriétés et méthodes de son prototype, l’équivalent d’une classe ‘père’ dont il hérite
- La fonction constructrice Type est appelée avec les arguments fournis,
thisétant lié au nouvel objet créé. - L’objet renvoyé par le constructeur devient le résultat de l’expression qui contient
new.
On peut étendre un type d’objet existant avec des propriétés ou des méthodes supplémentaires, en les ajoutant à son prototype :
> Array.prototype.toString = function() {
> return `tableau, taille ${this.length}`
> }
> [1, 2].toString()
'tableau, taille 2'
Le type Object possède des méthodes statiques très utiles, notamment :
Object.keys(obj): renvoie la liste des clés des propriétés propres deobj(sans ordre garanti)Object.values(obj): renvoie la liste des valeurs des propriétés propres deobj(sans ordre garanti)
L’expression booléenne x instanceof t permet de déterminer si x est du type t, par exemple myarray instanceof Array.
Tableaux avancés
Un tableau est en fait un objet du type Array, qui possède les constructeurs, propriétés et méthodes suivantes :
new Array(n): créé un tableau avecnemplacements vides, c’est à dire avec la propriétélengthàn- propriété
length: quand un élément est ajouté au tableau à la positioni,lengthprend la valeuri+1sii >= length - méthode
includes(elt): renvoie un booleéen indiquant sieltest inclus dans le tableau - méthode
indexOf(): retourne le premier (plus petit) index d’un élément égal à la valeur passée en paramètre à l’intérieur du tableau, ou -1 si aucun n’a été trouvé - méthode
join(sep): concatène tous les éléments d’un tableau en une chaîne de caractères, les éléments étant séparés parsep - méthode
slice(start, end): renvoie un objet tableau, contenant une copie superficielle de la portion du tableau original entre l’indicestart(inclus) et l’indiceend(exclu). Le tableau original n’est pas modifié - méthode
fill(val): remplit tous les éléments du tableau avec la valeurval - méthode
pop(): supprime le dernier élément d’un tableau et retourne cet élément - méthode
push(val[, val, ...]): ajoute un ou plusieurs éléments à la fin d’un tableau et retourne la nouvelle longueur du tableau - méthode
shift(): supprime le premier élément d’un tableau et retourne cet élément - méthode
sort(func): trie sur place le tableau à l’aide de la fonction de comparaisonfunc - méthode
splice(i, n): supprime sur placenéléments dans le tableau à partir de la positioni - méthode
concat(a1[, a2,...]): renvoie un nouveau tableau constitué de ce tableau concaténé aveca1,a2, etc. - méthodes
map,reduce,filter,find, ’forEach,some,every` qui permettent une programmation fonctionnelle (voir plus loin)
Fonctions avancées
Une fonction Javascript est un objet héritant du type prédéfini Function :
$ node
> parseInt
[Function: parseInt]
> parseInt instanceof Object
true
> parseInt instanceof Function
true
Écriture ‘fat arrow’ (depuis ES6) :
> let square = (x => x*x)
undefined
> typeof(square)
'function'
> square(2)
4
> let printSquare = (x => {
> console.log("Le carré de ", x, "est", x*x)
> })
> printSquare(2)
Le carré de 2 est 4Dans le corps d’une arrow function, this est le dictionnaire de son scope environnant lexical
Une fonction déclarée avec le mot-clé function est remontée dans le code (‘hoisting’) : cela permet de l’utiliser avant qu’elle ait été déclarée dans le code. Ce n’est pas le cas des fonctions anonymes affectées à des variables, dont la portée suit les règles habituelles.
Lors d’un appel de fonction :
- si le nombre d’arguments est supérieur au nombre de paramètres, les arguments supplémentaires sont ignorés
- si le nombre d’arguments est inférieur au nombre de paramètres, les paramètres manquants prennent la valeur
undefined, sauf si une valeur par défaut a été prévue. Exemple :
function f(a, b=2) {
console.log('a', a, 'b', b)
}
f(3, 4) // --> a 3 b 4
f(3) // --> a 3 b 2
String avancé
La chaine de caractères est un type primitif en javascript. Lorsqu’une instruction accède à des propriétés (telle que .length) et des méthodes (telles que .substring()) sur une chaine, celle-ci est temporairement convertie en un object de type String qui possède ces méthodes et propriétés. Après usage cet objet est candidat au garbage-collecting.
length: propriété, longueur de la chaine. Ne pas modifier.charAt(): Renvoie le caractère (ou plus précisement, le point de code UTF-16) à la position spécifiée.concat(): Combine le texte de deux chaînes et renvoie une nouvelle chaîne.includes(): Défini si une chaîne de caractères est contenue dans une autre chaîne de caractères.endsWith(): Défini si une chaîne de caractère se termine par une chaîne de caractères spécifique.indexOf(): Renvoie la position, au sein de l’objet String appelant, de la première occurrence de la valeur spécifiée, ou -1 si celle-ci n’est pas trouvée.lastIndexOf(): Renvoie la position, au sein de l’objet String appelant, de la dernière occurrence de la valeur spécifiée, ou -1 si celle-ci n’est pas trouvée.localeCompare(): Renvoie un nombre indiquant si une chaîne de référence vient avant, après ou est en position identique à la chaîne donnée selon un ordre de tri.match(): Utilisée pour faire correspondre une expression rationnelle avec une chaîne.matchAll(): Renvoie un itérateur listant l’ensemble des correspondances d’une expression rationnelle avec la chaîne.padEnd(): Complète la chaîne courante avec une autre chaîne de caractères, éventuellement répétée, afin d’obtenir une nouvelle chaîne de la longueur indiquée. La chaîne complémentaire est ajoutée à la fin.padStart(): Complète la chaîne courante avec une autre chaîne de caractères, éventuellement répétée, afin d’obtenir une nouvelle chaîne de la longueur indiquée. La chaîne complémentaire est ajoutée au début.repeat(): Renvoie une chaîne dont le contenu est la chaîne courante répétée un certain nombre de fois.replace(): Utilisée pour rechercher une correspondance entre une expression régulière et une chaîne, et pour remplacer la sous-chaîne correspondante par une nouvelle chaîne.search(): Exécute la recherche d’une correspondance entre une expression régulière et une chaîne spécifiée.slice(): Extrait une section d’une chaîne et renvoie une nouvelle chaîne.split(): Sépare un objet String en un tableau de chaînes en séparant la chaîne en plusieurs sous-chaînes.startsWith(): Détermine si une chaîne commence avec les caractères d’une autre chaîne.substr(): Renvoie les caractères d’une chaîne à partir de la position spécifiée et pour la longueur spécifiée.substring(): Renvoie les caractères d’une chaîne entre deux positions dans celle-ci.toLowerCase(): Renvoie la valeur de la chaîne appelante convertie en minuscules.toUpperCase(): Renvoie la valeur de la chaîne appelante convertie en majuscules.trim(): Retire les blancs en début et en fin de chaîne.trimStart(): Retire les blancs situés au début de la chaîne.trimEnd(): Retire les blancs situés à la fin de la chaîne.
Classes (depuis ES6)
Elles ont été introduites dans la syntaxe, mais restent du sucre syntaxique par dessus l’organisation objet basée sur des proptotypes.
Exemple:
class Rectangle {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.width * this.height;
}
}
class Square extends Rectangle {
constructor(size) {
super(size, size)
}
}
const square = new Square(100)
console.log('area', square.area)
Les déclarations de classes ne sont pas remontées dans le code comme les déclaration de fonctions.
truthy & falsy
$ node
> [0, 1, null, "", [], {}].map(x => console.log(x, x ? "is truthy" : "is falsy"))
0 is falsy
1 is truthy
null is falsy
is falsy
[] is truthy
{} is truthy
try / catch / finally
Problématique des modules
Les modules permettent de diviser un programme en différentes parties. Un module exporte des fonctions, classes etc. et peut importer les fonctions, classes etc. d’autres modules, appelés dépendances. Il existe différents formats de modules, incompatibles les uns avec les autres, mais c’est le format le plus récent ESM (Ecma Script Module) qui est amené à les remplacer tous.
Modules CJS (CommonJS)
Il s’agit du format de module historique de NodeJS, utilisé notamment dans les packages npm.
On utilise la fonction require pour importer un module.
Module externe npm
Module sous forme d’un fichier
$ cat math.js
module.exports = {
pi: 3.14159,
e: 2.71828,
square: function(x) { return x*x }
}
$ node
> const math = require('./math') // ou './math.js'
undefined
> math.e
2.71828
> math.square(5)
25Module sous forme d’un répertoire
Le répertoire doit contenir un fichier index.js
$ cat math/index.js
module.exports = {
pi: 3.14159,
e: 2.71828,
square: function(x) { return x*x }
}
$ node
> require('./math')
{ pi: 3.14159, e: 2.71828, square: [Function: square] }Variables visibles dans le scope d’un module
__dirname: chemin d’accès absolu au répertoire du module courantmodule: référence au module courantmodule.exports: définit ce qu’un module exporte, accessible parrequire()
Modules ESM
C’est le format de modules utilisé dans les navigateurs récents ; il devrait à terme remplacer tous les autres. Il est déjà compatible avec NodeJS version 14. Les modules ESM sont généralement stockés dans des fichiers d’extension .mjs.
On utilise le mot-clé import pour importer un module.
Exportations multiples
$ cat main.js
...
import { pi, square } from './math.mjs'
...
Exportation ‘default’
$ cat main.js
...
import math from './math.mjs'
...
let PI = math.pi
...
Promesses
Une promesse est un object renvoyé par une opération asynchrone (ex : requête HTTP) et auquel sont attachés des callback, lors des événements :
- de complétion de l’opération, avec renvoi d’une valeur : callback
.then(rep => ...; return value) - d’échec de l’opération : callback
.catch(err => ...) - de fin de l’opération, qu’elle ait échoué ou pas : callback
.finally(() => ...)
.then(), .catch() et .finally() renvoient la promesse elle-même, ce qui permet de les chainer afin d’écrire un code asynchrone de façon linéaire, ressemblant à un code synchrone.
Exemple :
$ npm install axios
...
$ node
> let axios = require('axios')
undefined
> axios.get("https://yesno.wtf/api")
... .then(rep => {
... console.log(rep.data)
... }
... .catch(err => {
... console.err(err.toString())
... }
... .finally(() => {
... console.log("The End")
... }
{ answer: 'no',
forced: false,
image: 'https://yesno.wtf/assets/no/25-55dc62642f92cf4110659b3c80e0d7ec.gif'
}
The EndDe nombreuses librairies de promesses existent, mais depuis ES6 la classe Promise est directement disponible et incorpore les fonctionnalités essentielles. Notamment :
Promise.all(<liste/iterable de promesses>): renvoie une promesse qui réussi lorsque toutes les promesses de la liste ont réussiPromise.resolve(valeur): renvoie une promesse qui réussi en renvoyantvaleurPromise.reject(err): renvoie une promesse qui échoue avec l’erreurerr
async / await (depuis ES6)
Ces mot-clés sont du sucre syntaxique qui permet l’utilisation de promesses de façon simplifiée. Ils doivent être préférés à l’utilisation directe des promesses, sauf cas particuliers tels que l’usage de Promise.all().
Exemple :
async function getYesNo() {
try {
let rep = await axios.get("https://yesno.wtf/api")
console.log(rep.data)
} catch(err) {
console.err(err.toString())
} finally {
console.log("The End")
}
}Programmation fonctionnelle avec map, forEach, reduce, filter, find

Les indices sont accessibles optionnellement :
Exemple : opérations ensemblistes
- intersection :
arr1.filter(x => arr2.includes(x)) - difference :
arr1.filter(x => !arr2.includes(x))
Déstructuration / restructuration
listes
$ node
> let [ a, b, ...others ] = [1, 2, 3, 4, 5]
undefined
> others
[3, 4, 5]
> [ 11, 22, ...others]
[11, 22, 3, 4, 5]dictionnaires
$ node
> let dict = { a: 1, b: 2, c: 3, d: 4 }
undefined
> let { b, a } = dict
undefined
> a
1
> b
2
> let { c, d, ...others } = dict
undefined
> others
{ a: 1, b: 2 }
> { x: 11, y: 22, ...others }
{ a: 1, b: 2, x: 11, y: 22 }Ce mécanisme permet d’écrire des fonctions avec l’équivalent d’un passage de paramètres par mots-clés :
Activités à réaliser
- Écrire une fonction qui inverse une chaine
- Écrire une fonction qui indique qu’une chaine est un palindrome
- Écrire une fonction qui renvoie un dictionnaire du nombre d’occurence de chaque lettre d’une chaine
- Écrire une fonction qui renvoie le caractère le plus fréquent dans une chaine (non vide)
Depuis ES7/ECMA2016
Set
$ node
> let ensemble = new Set([1, 2])
undefined
> ensemble.add(3); ensemble.add(2)
Set { 1, 2, 3 }
> ensemble.has(1)
true
> ensemble.has(4)
false
Map
TODO
Generateurs
$ node
> function *gen() { yield 1; yield 2; yield 3 }
undefined
> const g = gen()
undefined
> g
Object [Generator] {}
> g.next()
{ value: 1, done: false }
> g.next()
{ value: 2, done: false }
> g.next()
{ value: 3, done: false }
> g.next()
{ value: undefined, done: true }$ node
> function *fibonacci() { yield 1; yield 1; let prev=1; prevprev=1; while (true) { const curr=prev+prevprev; yield curr; [prevprev, prev] = [prev, curr] } }
undefined
> let fib = fibonacci()
undefined
> fib.next()
{ value: 1, done: false }
> fib.next()
{ value: 1, done: false }
> fib.next()
{ value: 2, done: false }
> fib.next()
{ value: 3, done: false }
> fib.next()
{ value: 5, done: false }
> fib.next()
{ value: 8, done: false }
> fib.next()
{ value: 13, done: false }Fermetures
Une fermeture est la paire formée d’une fonction et des références à son état environnant (l’environnement lexical).
En JavaScript, une fermeture est créée chaque fois qu’une fonction est créée.
function addTo(x) {
return function(y) {
return x + y
}
}
const add5 = addTo(5)
const add10 = addTo(10)
console.log(add5(2)) // 7
console.log(add10(2)) // 12
Les expressions régulières
TODO
Les événements
Le navigateur et NodeJS implémentent un mode de fonctionnement asynchrone basé sur les événements.
Dans le contexte du navigateur, les événements sont des interactions sur la page (souris, clavier etc.), des événements réseau, des déclenchements de timers, alors qu’en NodeJS les événements sont liés aux opérations asynchrones d’entrées/sorties (accès aux fichiers, réseau, etc.)
Javascript est un langage synchrone et mono-thread, mais son inclusion dans une ‘event-loop’ fournit l’impression d’une exécution asynchrone multi-thread :

Browser events
const event = new Event('build')
document.documentElement.addEventListener('build', (e) => {
console.log('a build event occurred!', e)
}, false)
document.documentElement.dispatchEvent(event)
Liste des événements navigateur : https://developer.mozilla.org/en-US/docs/Web/Events
NodeJS events
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()
myEmitter.on('event', (a, b) => {
console.log('an event occurred!', a, b)
})
myEmitter.emit('event', 11, 22)
Créer une commande shell avec NodeJS
Modifier package.json et ajouter l’entrée bin ; la propriété va devenir le nom de la commande :
{
"name": "jcbhello",
"version": "1.0.0",
"description": "jcb hello",
"bin": {
"hello": "./helloCmd.js"
}
}helloCmd.js
#!/usr/bin/env node
console.log("Hello, world!")
Installation de la commande (dans un des chemins de $PATH) :
