Ceci est un cours de
JavaScript
Front-end et Back-end
Qui suis-je ?
- Anthony Loup (contact: antho.iut@proton.me)
- IUT Bourg, Promo 2016
- INSA Lyon, Promo 2019
- Développeur web back-end
- 🏢 Daryl Social Software https://www.darylsoftware.com/emploi
- 📍 Annecy
- ⌚ 3 ans
- 🧑💻 Typescript, GraphQL, Vue.js
- Durée du module: 24h (4h/semaine)
- Javascript en long en large et de travers (4h)
- Introduction à Vue.js et Node.js (4h)
- Projet fullstack en groupe (16h)
- Evaluation DS (1h)
Slides: https://slides.com/antho_loup/deck
Cours et projet inspiré de: https://slides.com/benjichaz/but2
Au programme
Javascript
(en long en large et de travers)
1995 - Créé par Brenden Eich chez Netscape/Mozilla
1997 - Standardiser comme ECMAScript (ES1)
2015 - Version majeur ES6 (ES2015) - Supporté par 99% des navigateurs
Depuis 2015, une nouvelle version chaque année
Historique
Fiche technique de Javascript
- Pensé pour le web
- Interprété (V8 utilisé pour Google Chrome, Node.js)
- Orienté objet
- Typage dynamique et faible (conversion implicite)
- A inspiré: Typescript (superset de JS), CoffeeScript
- En constante évolution (/!\) avec ECMAScript
- Application diverse
- Web front
- Web back
- App mobile
- App desktop
- Système embarqué
- etc...
Marché du travail
Source: stackoverflow survey 2023, most popular technology by professional developers
Source: State of JS 2022
Salaires
// index.html
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
</body>
</html>
// script.js
window.addEventListener("load", () => {
let body = document.body;
body.textContent = "Hello world";
});
Intégration de JavaScript
// Commentaire monoligne
/*
Commentaire
multi
ligne
*/
Les commentaires
/**
* Calculates the sum of two numbers.
*
* @param {number} num1 - The first number.
* @param {number} num2 - The second number.
* @returns {number} The sum of num1 and num2.
*/
function addNumbers(num1, num2) {
return num1 + num2;
}
JSDoc
Les variables
let x = 42; // 42
x = 1337;
// 1337
let y; // undefined
const x = 42; // 42
x = 1337;
// Uncaught TypeError: invalid assignment to const 'x'
// DEPRECATED
var x = 42; // 42
Portée d'une variable
if(true) {
let x = 42;
x; // 42
}
x; // undefined
ES6
OLD
// DEPRECATED
var x = 42; // 42
// ... Complex code
var condition = true;
if(condition) {
var x = 421
}
// x = 421
Ne pas utiliser var
!
Nommage
Un nom de variable correct peut contenir :
- lettres minuscules (a-z)
- lettres majuscules (A-Z)
- chiffres (0-9)
- caractères dollar ($)
- caractères underscore (_)
Un nom de variable ne commence jamais par un chiffre.
good
GOOD
$Good
_good_
gôöd
b-a-d
-Bad-
84D
Types primitifs et objets
Types objets (Object) |
---|
Object |
Array |
Set |
Map |
RegExp |
Function |
... |
Types primitifs |
---|
string |
number |
boolean |
null |
undefined |
symbol |
BigInt |
ES6
ES11
"hello"
1337
true
null
undefined
Symbol(42)
BigInt('1337')
[42, "Hello", true]
{ foo: "bar" }
function f() {}
Typage faible
let x = 1337;
x; // 1337
typeof x; // "number"
x = new Date();
typeof x; // "object"
// Le type peut varier pendant l'exécution
x = new Date();
typeof x; // "object"
x instanceof Date; // true
x instanceof RegExp; // false
// Arithmétique
(2 + 13 - 7) * 2 / 4 % 5
// Affectation
let x = 2 // 2
x += 13 // 15
x -= 7 // 8
x *= 2 // 16
x /= 4 // 4
x %= 5 // 4
// In/Décrémentation
let x = 2 // 2
x++ // 2
++x // 4
x-- // 4
--x // 2
// Logique
true && false // false
true || false // true
// <=> true || (false && false) true
true || false && false
let z = true; !z // false
// Egalité
23 == 23 // true
23 == "23" // true
23 === "23" // false
0 == false // true
0 === false // false
// Non Egalité
23 != 23 // false
23 != "23" // false
23 !== "23" // true
// Inégalité
42 < 32; // false
42 <= 42; // true
42 > 6; // true
17 >= 42; // false
Les opérateurs
// Birwise
// // AND
3 & 5; // 1
// OR
3 | 5; // 7
// Shift
1 << 3; // 8
let number = getNumber()
if (number > 0) {
console.log("Positive")
} else if (number < 0) {
console.log("Negative")
} else if (number === 42) {
console.log("Life answer")
} else {
console.log("Zero")
}
let color = getColor()
switch (color) {
case "RED":
code = "#f00";
break;
case "GREEN":
code = "#0f0";
break;
default:
code = "#000";
}
let i = 0
while(i < 5) {
doSomething(i)
i++
}
for(let i = 0; i < 5; i++) {
doSomething(i)
}
Contrôle de flux
// condition ? siConditionVraie : siConditionFausse;
let x = age >= 18 ? "Majeur" : "Mineur";
Opérateur ternaire
Quizz 1
let maVariable;
maVariable; // ?
Quand une variable est déclarée mais non initialisée, elle vaut
false
null
undefined
Javascript lance une erreur
let maVariable;
maVariable; // undefined
Quand une variable est déclarée mais non initialisée, elle vaut
false
null
undefined
Javascript lance une erreur
Cette variable à un nom valide en JavaScript: $foo
Vrai
Faux
Cette variable à un nom valide en JavaScript: $foo
Vrai
Faux
Parmi ces types, lesquels sont primitifs ?
Array
string
Function
null
Parmi ces types, lesquels sont primitifs ?
Array
string
Function
null
Un typage dynamique veut dire...
Que le type d'une variable reste constant dans le temps
Qu'il existe plusieurs classes de type (primitifs, object, etc...)
Que le type d'une variable peut changer après initialisation
Que les variables ne peuvent pas être castées
Un typage dynamique veut dire...
Que le type d'une variable reste constant dans le temps
Qu'il existe plusieurs classes de type (primitifs, object, etc...)
Que le type d'une variable peut changer après initialisation
Que les variables ne peuvent pas être castées
Que vaut: typeof "42"
?
number
object
boolean
string
Que vaut: typeof "42"
?
number
object
boolean
string
Quelles valeurs retournent les deux évaluations ?
41 puis 42
42 puis 42
let x = 41;
x++; // ?
x; // ?
Quelles valeurs retournent les deux évaluations ?
41 puis 42
42 puis 42
let x = 41;
x++; // 41
x; // 42
Quelles sont les différences entre les opérateurs == et === ?
Le nombre de égale pour écrire l'opérateur
Ils font la même chose
=== est plus stricte, il vérifie le type
== est plus stricte, il vérifie le type
Quelles sont les différences entre les opérateurs == et === ?
Le nombre de égale pour écrire l'opérateur
Ils font la même chose
=== est plus stricte, il vérifie le type
== est plus stricte, il vérifie le type
Number
// Declarations
let a = 42;
let b = 3.;
let c = 3.14;
let d = -20;
let e = 10e5;
let f = 0x20; // 32
let g = Infinity;
let h = -Infinity;
let i = NaN; // Not a Number
let j = +0;
let h = -0;
// Float precision IEEE 754
0.1 + 0.2; // 0.3000000000004
// De Number to String
const x = 42;
String(x); // "42"
// De String à Number
const y = "42";
parseInt(y, 10); // 42
parseFloat(y); // 42
Math.round(100.7); // 101
Math.ceil(12.3); // 13
Math.floor(12.7); // 12
Math.random(); // 0.6493849611386405
String
const x = "I'm your \"father\"\n";
const y = 'I\'m your "father"\n';
const z = `I'm your "father"\n`;
const x = "foo";
x[0]; // "f"
x[1]; // "o"
x[2]; // "o"
x.length; // 3
// Concaténation d'un String
const x = "Veni";
const y = "vidi";
const z = "vici";
// Performant
x + " " + y + " " + z; // "Veni vidi vici"
// Lisible
`${x} ${y} ${z}`; // "Veni vidi vici"
Méthodes de String
const x = " JavaScript\n";
x.substring(4, 9); // "vaScr";
x.toUpperCase(); // " JAVASCRIPT\n"
x.toLowerCase(); // " javascript\n"
x.replace("a", "@"); // " J@vaScript\n";
x.replace("a", /@/g); // " J@v@Script\n";
x.indexOf("Java"); // 2
x.indexOf("Vue"); // -1
x.trim(); // "JavaScript";
let str = "Hello, world!"
str.startsWith("Hello") // true
str.endsWith("world!") // false
str.includes("world") // true
str = "abc"
str.repeat(3) // "abcabcabc"
str = "123"
str.padStart(5, "0") // "00123"
str.padEnd(5, "0") // "12300"
RegExp
const x = /^\d{4} \d{4} \d{4} \d{4}$/g; // Compact
const y = new RegExp("^\d{4} \d{4} \d{4} \d{4}$", "g"); // Performant
const x = "I love JavaScript, Vue.js, Vuex and Vue-router";
x.match(/[A-Z][^, ]+/g); // ["JavaScript", "Vue.js", "Vuex", "Vue-router"]
x.match(/\d/g); // null
Quizz 2
Parmi ces propositions, quels sont les valeurs correctes pour initialiser une variable de type number ?
NaN
0xffff
7
"1773"
Parmi ces propositions, quels sont les valeurs correctes pour initialiser une variable de type number ?
NaN
0xffff
7
"1773"
Pour convertir la variable string x = "99"
en number, je peux utiliser ?
parseFloat(x)
parseInt(x)
x.toNumber()
parseInt(x, 10)
Pour convertir la variable string x = "99"
en number, je peux utiliser ?
parseFloat(x)
parseInt(x)
x.toNumber()
parseInt(x, 10)
Je peux faire des calculs mathématiques grâce à l'objet global
Math
Mathematics
Je peux faire des calculs mathématiques grâce à l'objet global
Math
Mathematics
Je peux concaténer des strings via
"hello" . "world"
String("hello", "world")
"hello" + "world"
"hello".concat("world")
Je peux concaténer des strings via
"hello" . "world"
String("hello", "world")
"hello" + "world"
"hello".concat("world")
Je peux tester une expression régulière /regex/ sur une chaîne "foo" via
/maRegex/.match("foo")
"foo".match(/maRegex/)
match(/maRegex, "foo"/)
"maRegex".includes("foo")
Je peux tester une expression régulière /regex/ sur une chaîne "foo" via
/maRegex/.match("foo")
"foo".match(/maRegex/)
match(/maRegex, "foo"/)
"maRegex".includes("foo")
Array
Initialisation d'un Array
let x = [1, 2, 3];
let y = ["Hello", true, 42, {foo: "bar"}];
let z = [[1,2,3], [4,5,6]];
Lecture dans un Array
let x = [1, 2, 3];
x[0]; // 1
let y = ["Hello", true, 42, {foo: "bar"}];
x[3]; // {foo: "bar"}
let z = [[1,2,3], [4,5,6]];
x[1]; // [4,5,6]
x[1][2]; // 6
Écriture dans un Array
let x = [1, 2, 3];
x[0] = 42;
x; // [42, 2, 3]
Taille d'un Array
let x = [1, 2, 3];
x.length; // 3
Itération d'un Array
let digits = [1, 2, 3];
for(let i = 0; i < digits.length; i++) {
// digits[i];
}
for(let digit of digits) {
// digit;
}
digits.forEach(digit => {
// digit;
});
Manipulation d'un Array
let x = [12, 3, 1, 150];
x.push(17); // Ajoute 17 en fin de tableau
x; // [12, 3, 1, 150, 17]
x.pop(); // 17 - Supprime et retourne le dernier élement
x; // [12, 3, 1, 150]
x.unshift(42); // Ajoute 17 en début de tableau
x; // [42, 12, 3, 1, 150]
x.shift(); // 42 - Supprime et retourne le premier élement
x; // [12, 3, 1, 150]
x.splice(1, 2); // [12, 150] - Supprime 2 éléments à la position 1
Transformation d'un Array
let x = [12, 3, 1, 150];
x.map(element => element * 2); // [24, 6, 2, 300]
x.reduce((acc, element) => acc + element) // 166
x.sort();
x; // [1, 12, 150, 3]
x.sort((a, b) => a - b);
x; // [1, 3, 12, 150]
x.reverse()
x; // [150, 12, 3, 1]
Recherche dans un Array
let x = [12, 3, 1, 150];
x.indexOf(3); // 1 - Trouve l'index d'un élément
x.includes(3); // true - Indique la présence d'un élément
x.find(element => element === 3); // 3 - Trouve un élément
x.find(element => element === 0); // undefined
x.findIndex(element => element === 3); // 1 - Trouve l'index d'un élément
x.findIndex(element => element === 0); // -1
x.filter(element => element < 10); // [3, 1] - Filtre les éléments
Object
Initialisation d'un Object
// Object vide
let x = {};
let y = new Object();
let z = {
firstname: "John",
"lastname": "Doe",
age: 24,
'engineer': true,
company: "slides.com"
};
Propriétés d'un objet
let x = {
firstname: "John",
"lastname": "Doe",
age: 24,
'engineer': true,
company: "slides.com"
};
x.age; // 24
x['company']; // "slides.com"
Propriétés d'un objet
let x = {
firstname: "John",
"lastname": "Doe",
age: 24,
'engineer': true,
company: "slides.com"
};
x.age = 20;
x['company'] = "IUT Lyon 1";
x['passion'] = "Piano";
x;
// {
// firstname: "John",
// lastname: "Doe",
// age: 20,
// engineer: true,
// company: "IUT Lyon 1"
// passion: "Piano"
// }
Chaînage de propriétés
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
x.company.name; // "Google"
x.home; // undefined
x.home.address; // Uncaught TypeError: x.home is undefined
Chaînage optionnel
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
x.company.name; // "Google"
x?.home; // undefined
x?.home?.address; // undefined
Operateur in
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
'company' in x; // true
'home' in x; // false
Operateur delete
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
delete x.company;
x; // {firstname: "John"}
Lister les propriétés
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
Object.keys(x); // ["firstname", "company"]
Object.keys(x.company); // ["name", "address"]
Itérer sur les propriétés
let x = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
// Historique
for(let property in x) {
// x[property];
}
// Performance
Object.keys(x).forEach(property => {
// x[property];
})
Object.entries(x).forEach((key, value) => {
// value;
})
Affectation par composition
const firstname = "John";
const lastname = "Doe";
const company = {
name: "Google",
address: "California"
};
let x = {
firstname,
lastname,
company
};
x;
// {
// firstname: "John",
// lastname: "Doe",
// company: {
// name: "Google",
// address: "California"
// }
// }
Opérateur de reste et Objet
const firstname = "John";
const company = {
name: "Google",
address: "California"
};
let x = {
firstname,
...company
};
x;
// {
// firstname: "John",
// name: "Google",
// address: "California"
// }
Quizz 3
Dans un Array, je peux avoir des éléments de différents types
Oui
Non
Dans un Array, je peux avoir des éléments de différents types
Oui
Non
Dans un Array foo = ["x", "y", "z"]
, foo[2]
retourne
x
y
z
Une erreur
Dans un Array foo = ["x", "y", "z"]
, foo[2]
retourne
x
y
z
Une erreur
Je peux connaître la taille d'un Array foo via
foo.size
foo.length
foo.lenght
foo.dimensions
Je peux connaître la taille d'un Array foo via
foo.size
foo.length
foo.lenght
foo.dimensions
Utiliser des éléments de langage ES6 nativement implique
Que mon code est plus performant
Que monde code n'a pas de bug
Que certains navigateurs ne supportent pas mon code
Que mon code est plus joli
Utiliser des éléments de langage ES6 nativement implique
Que mon code est plus performant
Que mon code n'a pas de bug
Que certains navigateurs ne supportent pas mon code
Que mon code est plus joli
Je peux ajouter un élément à la fin de mon Array foo via
foo.push(1337)
foo[] = 1337
foo.add(1337)
foo += 1337
Je peux ajouter un élément à la fin de mon Array foo via
foo.push(1337)
foo[] = 1337
foo.add(1337)
foo += 1337
Je peux itérer sur un Array elements via
for(let element of elements) {...}
elements.forEach(element => {...})
for(const element of elements) {...}
loop(elements) {...}
Je peux itérer sur un Array elements via
for(let element of elements) {...}
elements.forEach(element => {...})
for(const element of elements) {...}
loop(elements) {...}
Pour transformer l'Array
[1, 2, 3] en [10, 20, 30], je peux utiliser
Array.forEach
Array.map
Array.reduce
Array.sort
Pour transformer l'Array
[1, 2, 3] en [10, 20, 30], je peux utiliser
Array.forEach
Array.map
Array.reduce
Array.sort
Dans la déclaration littérale d'un objet, les propriétés
doivent être écrites en "double quote"
peuvent être écrites en "double quote"
peuvent être écrites en `back quote`
peuvent être écrites sans être entourées
Dans la déclaration littérale d'un objet, les propriétés
doivent être écrites en "double quote"
peuvent être écrites en "double quote"
peuvent être écrites en `back quote`
peuvent être écrites sans être entourées
Un objet peut en imbriquer d'autres
Vrai
Faux
Un objet peut en imbriquer d'autres
Vrai
Faux
Je peux accéder à Google via...
infos.company.name
infos['company'].name
infos.name
Beaucoup de travail
let infos = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
Je peux accéder à Google via...
infos.company.name
infos['company'].name
infos.name
Beaucoup de travail
let infos = {
firstname: "John",
company: {
name: "Google",
address: "California"
}
};
Que vaut foo.bar
?
Une erreur
undefined
null
foo
let foo = {};
foo.bar; // ?
Que vaut foo.bar
?
Une erreur
undefined
null
foo
let foo = {};
foo.bar; // ?
Que vaut foo.bar.qux
?
Une erreur
undefined
null
foo
let foo = {};
foo.bar.qux; // ?
Que vaut foo.bar.qux
?
Une erreur
undefined
null
foo
let foo = {};
foo.bar.qux; // ?
Je peux tester si la propriété bar
existe ou non dans l'objet foo
via...
'bar' in foo
Object.keys
et Array.indexOf()
l'opérateur instanceof
le chainage optionnel
(?.
)
et l'operateur ===
Je peux tester si la propriété bar
existe ou non dans l'objet foo
via...
'bar' in foo
Object.keys
et Array.indexOf()
l'opérateur instanceof
le chainage optionnel
(?.
)
et l'operateur !
==
Que vaut data
?
Réponse A
Réponse B
Réponse C
Réponse D
let width = 67
let height = 23
let measures =
{ width, height }
let color = "red"
let texture = "vinyle"
let data = {
color,
texture: "iron",
...measures
}
{ // Réponse A
color: "red",
texture: "vinyle",
measures: {
width: 67,
height: 23
}
}
{ // Réponse C
color: "red",
texture: "iron",
width: 67,
height: 23
}
{ // Réponse B
color: undefined,
texture: "iron",
width: 67,
height: 23
}
{ // Réponse D
color: "red",
texture: "iron",
measures: {
width: 67,
heigth: 23
}
}
Que vaut data
?
Réponse A
Réponse B
Réponse C
Réponse D
let width = 67
let height = 23
let measures =
{ width, height }
let color = "red"
let texture = "vinyle"
let data = {
color,
texture: "iron",
...measures
}
{ // Réponse A
color: "red",
texture: "vinyle",
measures: {
width: 67,
height: 23
}
}
{ // Réponse C
color: "red",
texture: "iron",
width: 67,
height: 23
}
{ // Réponse B
color: undefined,
texture: "iron",
width: 67,
height: 23
}
{ // Réponse D
color: "red",
texture: "iron",
measures: {
width: 67,
heigth: 23
}
}
Que vaut data
?
Réponse A
Réponse B
Réponse C
Réponse D
let width = 67
let height = 23
let weight = 19
let measures =
{width, height, weight}
let data = {
weight: 42,
texture: "iron",
...measures
}
{ // Réponse A
texture: "iron",
weight: 42,
measures: {
width: 67,
height: 23,
weight: 19,
}
}
{ // Réponse C
weight: 19,
texture: "iron",
width: 67,
height: 23,
}
{ // Réponse B
texture: "iron",
width: 67,
height: 23,
weight: 19,
}
{ // Réponse D
texture: "iron",
measures: {
width: 67,
heigth: 23,
weight: 19,
},
}
Que vaut data
?
Réponse A
Réponse B
Réponse C
Réponse D
let width = 67
let height = 23
let weight = 19
let measures =
{width, height, weight}
let data = {
weight: 42,
texture: "iron",
...measures
}
{ // Réponse A
texture: "iron",
weight: 42,
measures: {
width: 67,
height: 23,
weight: 19,
}
}
{ // Réponse C
weight: 42,
texture: "iron",
width: 67,
height: 23,
}
{ // Réponse B
texture: "iron",
width: 67,
height: 23,
weight: 19,
}
{ // Réponse D
texture: "iron",
measures: {
width: 67,
heigth: 23,
weight: 19,
},
}
Set et Map
Set
let x = new Set();
x.add(42); // { 42 }
x.add("foo"); // { 42, "foo" }
x.has("foo"); // true
x.has(1337); // false
x.delete("foo");
x.size; // 1
for (let item of x) {
// items;
}
Map
let x = new Map();
x.set(42, "quarante-deux'");
x.set("foo", "f o o");
x.size; // 2
x.get(42); // "quarante-deux";
x.get("foo") // "f o o"
x.delete("foo");
x.forEach((value, key) => {
// value;, key;
});
Date
Initialisation d'une Date
let a = new Date() // Maintenant
let b = new Date(1537621200000) // Milisecondes depuis l'epoch Unix
let c = new Date('2018-09-22T15:00:00+02:00') // Format ISO 8601
let d = new Date(2018, 8, 22) // Le mois est 0-indexé
ISO 8601
Formatter d'une Date
let x = new Date('2018-09-22T15:00:00+02:00')
x.getTime(); // 1537621200000
x.toLocaleDateString(); // "2018-9-22"
x.toLocaleTimeString(); // "15:00:00"
x.toISOString(); // "2018-09-22T13:00:00.000Z"
ISO 8601
Manipuler une Date
let x = new Date('2018-09-22T15:00:00+02:00');
x.getDate(); // 22
x.setDate(30); // Date { 2018-09-30T13:00:00.000Z }
// Je vous épargne tout les getters/setters
Maintenant
let now = new Date().getTime();
let now = Date.now();
JSON
JavaScript Object Notation
JSON est une syntaxe pour sérialiser des objets, tableaux, nombres, chaînes de caractères, booléens et valeurs null.
Elle est basée sur la syntaxe de JavaScript mais en est distincte : du code JavaScript n’est pas nécessairement du JSON, et du JSON n’est pas nécessairement du JavaScript.
JSON invalide
{
firstname: "John",
'lastname': 'Doe',
"age": 024,
"company": {
`hiring`: true,
"nextWebTalent": undefined
},
"examDate": null,
"courses": ['JavaScript', "Vue.js"],
}
JSON invalide
{
firstname: "John",
'lastname': 'Doe',
"age": 024,
"company": {
`hiring`: true,
"nextWebTalent": undefined
},
"examDate": null,
"courses": ['JavaScript', "Vue.js"],
}
JSON valide
{
firstname: "John",
"lastname": "Doe",
"age": 24,
"company": {
"hiring": true
},
"examDate": null,
"courses": ["JavaScript", "Vue.js"]
}
Encoder et décoder en JSON
let decoded = { firstname: 'John', age: 24, company: "Google" };
JSON.stringify(decoded);
// '{"firstname":"John","age":24,"company":"Google"}'
let encoded = '{"firstname":"John","age":24,"company":"Google"}';
JSON.parse(encoded);
// {
// firstname: 'John',
// age: 24,
// company: 'Google'
// }
Les fonctions
Déclaration / Appel d'une fonction
function maFonctionInutile() {
}
maFonctionInutile(); // undefined
function addition(a, b, c = 0) {
return a + b + c;
}
addition(40, 2); // 42
addition(40, 2, 1); // 43
Fonction anonyme
const addition = function (a, b, c = 0) {
return a + b + c;
}
addition(40, 2); // 42
addition(40, 2, 1); // 43
Fonction fléchée
const addition = (a, b, c = 0) => {
return a + b + c;
}
addition(40, 2); // 42
addition(40, 2, 1); // 43
const majuscule = str => str.toUpperCase();
majuscule("hello world"); // "HELLO WORLD"
Opérateur de reste et Function
const casting = (a, b, ...plusEncore) => {
let liste = `${a} et ${b}`;
if(plusEncore.length > 0) {
liste += ` (et ${plusEncore.join(", ")})`;
}
return liste;
};
casting("Alice", "Bob");
// "Alice et Bob"
casting("Alice", "Bob", "Charlie", "Dan");
// "Alice et Bob (et Charlie, Dan)"
Passage par copie / référence
function exclamer(str) {
str += " !";
return str;
}
// Type primitif
let x = "Hello world";
exclamer(x); // "Hello world !"
x; // "Hello world"
x = exclamer(x);
x; // "Hello world !"
!!!
function exclamer(obj) {
obj.y += " !";
}
// Type objet
let x = {};
x.y = "Hello world";
exclamer(x);
x.y; // "Hello world !"
Type primitif passé par copie
Type objet passé par référence
Quizz 4
Set et Map sont de types objets ?
Oui
Non
Set et Map sont de types objets ?
Oui
Non
Une Date JavaScript peut être initialisée à partir de
D'une chaine au format ISO 8601
D'un timestamps UNIX en seconde
De rien
D'une chaine formater au format Français
Une Date JavaScript peut être initialisée à partir de
D'une chaine au format ISO 8601
D'un timestamps UNIX en seconde
De rien
D'une chaine formater au format Français
JSON
est un format de donnée standardisé
n'est pas JavaScript
JSON
est un format de donnée standardisé
n'est pas JavaScript
On peut sérialiser en JSON via
JSON.encode()
JSON.serialize()
JSON.stringify()
new JSON()
On peut sérialiser en JSON via
JSON.encode()
JSON.serialize()
JSON.stringify()
new JSON()
Une fonction
peut avoir des arguments par défaut
peut s'appeler elle même
peut être stockée dans une variable
retourne null si rien n'est retourné via le mot clé return
Une fonction
peut avoir des arguments par défaut
peut s'appeler elle même
peut être stockée dans une variable
retourne null si rien n'est retourné via le mot clé return
L'opérateur de reste (...
) dans une fonction permet
D'ignorer les paramètres
Récupérer les paramètres dynamiquement
L'opérateur de reste (...
) dans une fonction permet
D'ignorer les paramètres
Récupérer les paramètres dynamiquement
Dans une fonction, un paramètre de type number
serait passé par copie
Vrai
Faux
Cela dépend
Dans une fonction, un paramètre de type number
serait passé par copie
Vrai
Faux
Cela dépend
Dans une fonction un paramètre de type Date
serait passer par copie
Vrai
Faux
Cela dépend
Dans une fonction un paramètre de type Date
serait passer par copie
Vrai
Faux
Cela dépend
Les erreurs
try catch
try {
vivreTranquille();
} catch (e) {
if(e.message === "exam") {
reviser();
}
}
function vivreTranquile() {
// ...
throw new Error("exam");
// ...
}
Programmation asynchrone
La boucle d'événement
-
-
-
File
d'événements
1 contexte
à la fois
Gére l'exécution asynchrone des événements
Fonction de rappel
document.body.addEventListener('click', () => {
alert("Hello world");
})
Événement après un délai
console.log("Le petit oiseau va sortir dans 2 secondes !")
setTimeout(() => {
console.log("Coucou !");
}, 2000);
Événement récurrent
let compteur = 10;
setInterval(() => {
if(compteur === 0) {
console.log("BOOM !")
}
else if(compteur > 0) {
console.log(compteur);
}
compteur--;
}, 1000);
Les promesses
Les promesses
En attente
Résolue
Rejetée
Les promesses
En attente
Résolue
Rejetée
Résultat à l'exam
Exam passé
Exam échoué
Les promesses
const promise = new Promise((resolve, reject) => {
let examResult = undefined;
while(examResult === undefined) {
examResult = resultAreOut();
}
if(examResult) {
resolve(getExamGrade());
} else {
reject(getExamRetryDate());
}
})
promise.then((grade) => {
if(grade > 15) {
console.log('Very nice !');
} else {
console.log('Nice...');
}
}).catch((retryDate) => {
console.log('Retry exam date', retryDate);
})
async / await
const promise = new Promise((resolve, reject) => {
let examResult = undefined;
while(examResult === undefined) {
examResult = resultAreOut();
}
if(examResult) {
resolve(getExamGrade());
} else {
reject(getExamRetryDate());
}
})
async function () {
try {
let grade = await promise;
//...
} catch(retryDate) {
retryDate;
//...
}
}
Déboguer malin
Logguer en console
let maVariable = 42;
console.log(maVariable);
// > 42
console.log({maVariable});
// > { maVariable: 42 }
//
console.info("foo");
console.warn("bar");
console.error("qux");
Plus loin avec la console
// Effacer la console
console.clear();
// Afficher la pile d'appel
console.trace();
// Mesurer le temps
console.time("mesurer durée traitement");
console.timeEnd("mesurer durée traitement");
Le débogueur
debugger;
Les modules
Import export
// module.js
export default {
name: "John"
};
export const email = "jhon@doe.pro";
let website = "doe.pro";
export website;
// main.js
import Module from "./module.js";
Module; // { name: "John" }
import { email } from "./module.js";
email; // "john@doe.pro"
import { website as url } from "./module.js";
url; // "doe.pro"
// Une seul ligne
import Module, { email, website as url } from "./module.js";
Quizz 5
JavaScript n’exécute jamais deux contextes d’exécution en parallèle
Sauf bug du moteur Javascript, c'est vrai
Faux
Ca dépend
JavaScript n’exécute jamais deux contextes d’exécution en parallèle
Sauf bug du moteur Javascript, c'est vrai
Faux
Ca dépend
Une fonction de rappel est une fonction passée en paramètre d'une fonction pour être appelée a posteriori
Faux
Vrai
Une fonction de rappel est une fonction passée en paramètre d'une fonction pour être appelée a posteriori
Faux
Vrai
Les promesses...
peuvent ne jamais être résolu
ont un nombre infini d'états possibles
ont 3 états possibles
ont 4 états possibles
Les promesses...
peuvent ne jamais être résolu
ont un nombre infini d'états possibles
ont 3 états possibles
ont 4 états possibles
await
permet de bloquer un contexte d’exécution en attendant la fin d'une promesse
Faux
Vrai
await
permet de bloquer un contexte d’exécution en attendant la fin d'une promesse
Faux
Vrai
x vaut 42 après un import x from "modules.js"; si dans ce fichier référencé j'ai fais
export 42;
export default 42;
export const x = 42;
export { 42 }
x vaut 42 après un import x from "modules.js"; si dans ce fichier référencé j'ai fais
export 42;
export default 42;
export const x = 42;
export { 42 }
y vaut "foo" après un import { y } from "modules.js"; si dans ce fichier référencé j'ai fais
export "foo";
export default "foo";
export const y = "foo";
export {y: "foo"}
y vaut "foo" après un import { y } from "modules.js"; si dans ce fichier référencé j'ai fais
export "foo";
export default "foo";
export const y = "foo";
export {y: "foo"}
Exercices
https://github.com/Antloup/js-exercises
DOM API
Document Object Model
-
Représente la page web sous forme d'arbre de nœuds
-
Chaque nœud représente un élément HTML, un attribut, du texte ou un commentaire
-
Manipulable (ajout, modification, suppression) par une API
-
Utilisé pour créer des applications web interactives et dynamiques.
-
Standard du World Wide Web Consortium (W3C) / JS
Accès à un élément
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
document.body;
// <body>
document.getElementById("titre");
// <input id="titre">
document.querySelectorAll("*");
// [<html>, <head>, ...]
window.title;
// "Apprenons JS"
Manipuler les attributs
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let input = document.querySelector("#titre");
// Valeur du champ
input.value;
input.getAttribute("type"); // "text"
input.setAttribute("type", "password");
Manipuler les nœuds textes
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<p>Hello<br />world</p>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let paragraph = document.querySelector("p");
paragraph.textContent;
// "Hello world"
paragraph.innerHTML;
// "Hello<br />world"
paragraph.outerHTML;
// "<p>Hello<br />world</p>"
Manipuler les classes
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1 class="head">
Hello world
</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let header = document.querySelector("h1");
header.classList.contains('head');
// true
header.classList.add('strong');
header.classList.remove('head')
Événements DOM
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1>Hello<br />world</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let bouton = document.querySelector("button");
bouton.addEventListener("click", () => {
alert("Clicked !");
});
// Il y a de nombreux événements écoutables
//
// mousedown, mouseup, mousemove, click, dblclick
// keydown, keyup, keypress
// scroll
// ...
Créer un noeud
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Apprenons JS</title>
<script src="script.js"></script>
</head>
<body>
<h1 class="head">
Hello world
</h1>
<input id="titre" type="text" />
<button>Envoyer</button>
</body>
</html>
let img = document.createElement("IMG");
document.body.appendChild(img);
Exercice : Todo list
https://glitch.com/edit/#!/iut-todo-list-js
Todo list en Vanilla JS
<body>
<ul></ul>
<input type="text" />
<button>Ajouter</button>
</body>
let ul = document.querySelector("ul");
let input = document.querySelector("input");
let button = document.querySelector("button");
button.onclick = () => {
let item_li = document.createElement("LI");
let item_span = document.createElement("SPAN");
item_span.textContent = input.value;
item_li.appendChild(item_span);
let item_button = document.createElement("BUTTON");
item_button.textContent = "X";
item_button.onclick = () => {
item_span.style.textDecoration = "line-through";
};
item_li.appendChild(item_button);
ul.appendChild(item_li);
input.value = "";
};
Séance 2
Todo list: Problèmes
-
Modèle de données
-
Couplé fortement à la vue
-
Non centralisé
-
Non écoutable
-
-
Vue
- Mise à jour à la charge du développeur
- Complexité de compréhension du code
Solution
Vue
Logique
Modèle
Modifie
Actualise
Appel
// View
let ul = document.querySelector("ul")
let input = document.querySelector("input")
let button = document.querySelector("button")
function render() {
ul.innerHTML = ""
input.onkeyup = () => updateNewLabel(input.value)
for (let todo of todos) {
let item_li = document.createElement("LI")
let item_span = document.createElement("SPAN")
item_span.textContent = todo.label;
item_span.style.textDecoration = todo.done
? "line-through" : ""
item_li.appendChild(item_span)
let item_button = document.createElement("BUTTON")
item_button.textContent = "X"
item_button.onclick = () => removeTodo(todo);
item_li.appendChild(item_button)
ul.appendChild(item_li)
}
input.value = newLabel
button.onclick = () => addTodo()
}
<body>
<ul></ul>
<input type="text" />
<button>Ajouter</button>
</body>
// Model
let todos = []
let newLabel = ""
// Actions
function addTodo() {
todos.push({
label: newLabel,
done: false
});
newLabel = ""
render()
}
function removeTodo(todo) {
todo.done = true
render()
}
function updateNewLabel(_newLabel) {
newLabel = _newLabel
render()
}
Front-end / Back-end
Frontend / Backend
Utilisateur
Frontend
Backend
Database
</>
Frontend / Backend
Front-end
Front-end
- Partie visible et interactive d'une application web
- C'est ce avec quoi l'utilisateur interagit directement
- UI/UX : boutons, formulaires, images, etc.
- Techno HTML, CSS et JavaScript
- Exigences : UI/UX efficace, rapidité/fluidité, compatibilité, accessibilité, sécurité, maintenabilité
Frameworks
https://dayssincelastjavascriptframework.com/
Vue.js
Framework JavaScript pour le développement d'application web
Simplifie le développement d'interface dynamique
Populaire : Adobe, GitLab, ...
Historique
Juil 2013 - Premier commit par Evan You (@Google Creative Lab)
Fév 2014 - Version 0.9 - Première annonce publique
Oct 2015 - Version 1.0
Sep 2016 - Version 2.0
Fév 2022 - Version 3.0
Pourquoi Vue.js ?
Réelle plus value
Puissant
Léger
Open source
Populaire
Communauté active
Maintenu
Bien documenté
Grosso modo, que fait Vue ?
Permet de séparer la vue, le modèle de données et la logique applicative
- Gère à notre place le rendu de la vue, et la mise à jour des données via un DOM Virtuel
- Gère à notre place le binding événementiel
Paradigme
Vue
Logique
Modèle
Modifie
Actualise
Appel
Paradigme
Vue
?
Modèle
Modifie
?
?
Installation
npm init vue@latest
Intégration
// Version de développement
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
// Version de production
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
// Plus usuellement
import { createApp } from "vue"
Instance
<div id="app"></div>
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount("#app")
Architecture
Get started
Modèle, logique et vue
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<style scoped>
h1 {
text-decoration: underline;
}
</style>
DiscoverHelloWorld
Réactivité
Mise à jour automatique de l'UI et des données en fonction des mutations dans les données
Basé sur le design pattern Observer
Développement plus simple
et efficace
Réactivité : illustration
<script setup>
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
DiscoverReactivity
Attribut dynamique
<script setup>
import { ref } from 'vue'
const url = ref("learn.png")
setTimeout(() => {
url.value = "master.png"
}, 5000)
</script>
<template>
<img :src="url" />
</template>
DiscoverTemplate
Rendu conditionnel
<script setup>
import { ref } from "vue"
const speed = ref(10)
</script>
<template>
<p v-if="speed < 88">
Plus vite Doc !
</p>
<p v-else-if="speed === 88">
88 mph !
</p>
<p v-else>Retour vers le futur !</p>
<pre>
En console, entrer :
window.speed.value = 100
</pre>
</template>
DiscoverCondition
Boucles
<script setup>
import { ref } from "vue"
const verbs = ref([
{ id: 0, text: "Veni" },
{ id: 1, text: "Vidi" }
{ id: 2, text: "Vici" }
])
</script>
<template>
<ul>
<li
v-for="verb in verbs"
:key="verb.id"
>
{{ verb.text }}
</li>
</ul>
</template>
DiscoverLoop
Boucles avec index
<script setup>
import { ref } from "vue"
const verbs = ref([
{ id: 0, text: "Veni" },
{ id: 1, text: "Vidi" }
{ id: 2, text: "Vici" }
])
</script>
<template>
<ul>
<li
v-for="(verb, index) in verbs"
:key="verb.id"
>
#{{ index }} {{ verb.text }}
</li>
</ul>
</template>
DiscoverVForIndexed
Événements utilisateurs
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment">
Incrémenter !
</button>
</template>
DiscoverEvent
Gestionnaire paramétré
<script setup>
import { ref } from "vue"
const count = ref(0)
function increment(delta) {
count.value += delta
}
</script>
<template>
<p>{{ count }}</p>
<button @click="increment(3)">
Incrémenter !
</button>
</template>
DiscoverEventHandlerWithParameters
Binding bidirectionnel
<script setup>
import { ref } from "vue"
const input = ref("Hello !")
</script>
<template>
<input v-model="input" />
<p>{{ input }}</p>
<button @click="input = 'Hello !'">
Reset
</button>
</template>
DiscoverVmodel
Style dynamique
<script setup>
import { ref } from "vue"
const size = ref(10)
</script>
<template>
<button @click="size = 10">Small</button>
<button @click="size = 20">Medium</button>
<button @click="size = 30">Large</button>
<h1 :style="{fontSize: size + 'px'}">
Hello world
</h1>
</template>
DiscoverStyle
Classe dynamique
<script setup>
import { ref } from "vue"
const isRed = ref(true)
</script>
<template>
<h1 class="text"
:class="{highlight: isRed}"
>
Hello world
</h1>
<button @click="isRed = !isRed">
Toggle
</button>
</template>
<style scoped>
.highlight { color: red; }
.text { font-style: italic; }
</style>
DiscoverClass
Les composants
// LightSaber.vue
<script setup>
</script>
<template>
<span>Yoda</span>
<span class="handle">|||||</span>
<span class="light">======</span>
</template>
<style scoped>
.handle {
display: inline-block;
background: grey;
}
.light {
display: inline-block;
background: lightgreen;
color: transparent;
}
</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber />
</template>
DiscoverComponent
Les props
// LightSaber.vue
<script setup>
const props = defineProps({
owner: String,
color: String
})
</script>
<template>
<div>
<span>{{ owner }}</span>
<span class="handle">|||||</span>
<span class="light"
:style="{background: color}">
======
</span>
</div>
</template>
<style scoped>...</style>
<script setup>
import LightSaber from "./LightSaber.vue"
</script>
<template>
<LightSaber owner="Mace windu" color="magenta" />
</template>
DiscoverProp
Les props et les boucles
<script setup>
import LightSaber from "./LightSaber.vue"
const jedis = ref([
{ name: 'Obi-Wan', color: 'lightblue' },
{ name: 'Darth Vader', color: 'red' },
{ name: 'Rey', color: 'orange' },
])
</script>
<template>
<LightSaber
v-for="jedi in jedis"
:owner="jedi.name"
:key="jedi.name"
:color="jedi.color"
/>
</template>
DiscoverPropLoop
Propriété calculée
Problématiques
<div>
{{ message.split('').reverse().join('') }} <!-- #1 Sémantique difficile -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #2 Performance dégradée -->
</div>
...
<div>
{{ message.split('').reverse().join('') }} <!-- #3 Répétition évitable -->
</div>
Propriété calculée / Computed
<script setup>
import { ref, computed } from "vue"
const message = ref("Hello world")
const reversed = computed(() =>
message.value
.split('')
.reverse()
.join('')
)
// Une computed est une référence calculée à partir d'une expression
// Elle se met à jour automatiquement de façon réactive
// C'est une sorte de référence en lecture seule
</script>
<template>
<p>Message original : {{ message }}</p>
<p>Message inversé : {{ reversed }}</p>
</template>
DiscoverComputed
computed vs function
<script setup>
// ...
const reverse = () =>
message.value
.split('')
.reverse()
.join('')
</script>
<template>
Message inversé : {{ reverse() }}
</template>
const now = computed(() =>
Date.now()
)
// Une computed n'est recalculée que sur des références réactives
Différence : computed met en cache son résultat alors qu'une fonction est executé à chaque rendu de la vue
Filtrage d'un tableau
<script setup>
import { ref, computed } from "vue"
const numbers = ref([1, 2, 3, 4, 5])
const even = computed(() => {
return numbers.value.filter(
n => n%2 === 0
)
}) // [2, 4]
</script>
<template>
<li v-for="n in even">{{ n }}</li>
</template>
DiscoverListFiltering
Mutation d'un tableau
<script setup>
import { ref } from "vue"
let letters = ref(
["J", "X", "O", "B", "U", "I"]
)
let sort = () => letters.value.sort()
</script>
<template>
<div @click="sort">
{{ letters }}
</div>
</template>
Méthodes de mutations réactives
push(), pop(), shift(), unshift(),
splice(), sort(), reverse()
DiscoverListMutation
Vue.js devtools
Explorer le VDOM
Exercice : List Filtrable
1) Réimplementer la todo list
2) Ajouter un champ pour filtrer la liste des tâches par label.
3) Ajouter un select pour filtrer la liste des tâches par status
components/TodoList.vue
Connecter un backend
Fetch API
function getHelloWorld() {
const promise = fetch('https://www.xul.fr/ajax/ajax-get.json')
promise.then(res => res.json()) // { message: "Hello World !" }
}
// On préférera utiliser la syntaxe async/await
Fetch API + async/await
async function getHelloWorld() {
const res = await fetch('https://www.xul.fr/ajax/ajax-get.json')
const obj = await res.json() // { message : "Hello World !" }
}
Corps de réponse
async function getSomething() {
const res = await fetch('https://dummyjson.com/products/3')
// res.text()
// res.json()
// res.blob()
// res.arrayBuffer()
// res.formData()
}
En-têtes de requête
async function getProducts() {
const res = await fetch('https://dummyjson.com/products/', {
headers: {
Authorization: `Token MY_API_KEY`,
Accept: 'application/json',
},
})
return await res.json()
}
Corps de requête
async function putProduct() {
const res = await fetch('https://dummyjson.com/products/', {
method: 'PUT',
headers: {
Content-Type: 'application/json',
},
body: JSON.stringify({ name: 'Foo', description: 'Lorem ipsum' })
})
return await res.json()
}
Status de réponse
async function getNotFound() {
const res = await fetch('https://www.jack-ceparou.com/olydri')
// res.status // 404
// res.statusText // "Not Found"
}
En-têtes de réponse
async function getDocumentations() {
const res = await fetch('https://developer.mozilla.org')
res.headers.get("Content-type") // "text/html; charset=utf-8"
for(let [header, value] of res.headers.entries()) {
console.log(header, value)
}
}
Gestion d'erreur
async function getDocumentations() {
try {
const res = await fetch(`http://somewhere.com/somewhat`)
if (!response.ok) {
throw new Error('Fail during fetching somewhat');
}
return await response.json();
} catch (error) {
console.error(error);
}
}
Exercice : Connecter un backend
Specs README : https://github.com/Antloup/todo-list-server
1) Récupérer toutes les tâches
2) Permettre de filtrer par owner (computed)
3) Pouvoir enregistrer une nouvelle tâche dans le backend
4) Pouvoir passer à done une tâche
Back-end
Back-end
- "Côté serveur" d'une application web
- Traitement et stockage de données (logique applicative, base de données, service tiers, etc)
- Techno PHP, ASP, Ruby on Rails, J2EE, Node.js, ...
- Exigences : nombre de requêtes parallèle, temps de réponse, sécurité des données
API
-
Application Programming Interface
-
Fourni un accès structuré à une application ou un service
-
Basé sur un protocole (ex: HTTP, TCP, ...) et un standard (ex: XML, JSON, ...) de communication
-
Architectures populaires : REST, SOAP
API REST
-
Representational State Transfer
-
Architecture logicielle pour système distribué largement utilisé pour les API web
-
Basé sur HTTP pour effectuer des opérations CRUD (Create, Read, Update, Delete) via les méthodes/verbes HTTP POST, GET, PUT ou PATCH, DELETE sur des ressources
-
Données au format JSON ou plus rarement XML
Node.js
- Environnement JavaScript open source
- Basé sur le moteur JavaScript V8 de Google Chrome
- Hautement événementielle : adapté aux I/O intensifs (streaming, temps réel, traitement de données en masse, ...)
- NPM : Écosystème riche de modules et de bibliothèques
- Multiplateforme: Windows, Linux et macOS.
- Paradigme Node.js != Paradigme PHP
Express.js
-
Framework back-end open-source pour Node.js.
-
Fonctionnalités : routage, gestion des requêtes et des réponses HTTP, authentification et bien plus encore.
-
Adapté pour la création d'API
Installation
npm install express
Get started
import express from 'express'
const app = express()
app.listen(3000, () => {
console.log('Server is listening on port 3000 !')
})
Hello world
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('Hello world !');
})
app.listen(3000, () => {
console.log('Server is listening on port 3000 !')
})
Méthodes HTTP
app.get('/', (req, res) => {
res.send('GET at root');
})
app.post('/', (req, res) => {
res.send('POST at root');
})
app.put('/', (req, res) => {
res.send('PUT at root');
})
app.delete('/', (req, res) => {
res.send('DELETE at root');
})
Routage
app.get('/', (req, res) => {
res.send('GET at root');
})
app.get('/foo', (req, res) => {
res.send('GET at /foo');
})
Corps de réponse
app.get('/asHTML', (req, res) => {
res.send('GET at root'); // Content-Type : text/html
})
app.get('/asJSON', (req, res) => {
res.send({ foo: 'bar' }); // Content-Type : application/json
})
app.get('/asBinary', (req, res) => {
res.send(Buffer.from('baz')); // Content-Type : application/octet-stream
})
app.get('/fromFile', (req, res) => {
res.sendFile('/path/to/image.png');
})
En-tête de réponse
app.get('/notFound', (req, res) => {
res.status(404)
res.end()
})
app.get('/headers', (req, res) => {
res.set('Access-Control-Allow-Origin', '*')
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
})
res.end()
})
Redirections
app.get('/redirection', (req, res) => {
res.redirect('/foo/bar')
// res.redirect('http://example.com')
// res.redirect(301, 'http://example.com')
// res.redirect('../login')
})
Fichiers statiques
app.use(express.static('public'))
// http://localhost:3000/js/app.js
// http://localhost:3000/images/bg.png
// http://localhost:3000/hello.html
app.use('/static', express.static('public'))
// http://localhost:3000/static/js/app.js
// http://localhost:3000/static/images/bg.png
// http://localhost:3000/static/hello.html
Middleware globaux
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.use((req, res, next) => {
console.log(`Request received at ${new Date()}`)
next() // Call the next middleware function
})
app.use((req, res, next) => {
res.setHeader('X-Custom-Header', 'Hello World')
next() // Call the next middleware function
})
Middleware locaux
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization.split(' ')[1]
if(verifyToken(token)) {
next() // Call the next middleware function
} else {
res.status(401).json({ error: "Not authorized" })
}
}
app.get('/accounts', authMiddleware, (req, res) => {
res.send('Safe Hello World!')
})
Corps de requête JSON
import express from 'express'
const app = express()
app.use(express.json())
app.post('/users', (req, res) => {
const { name, email } = req.body
res.send(`User created: ${name} (${email})`)
})
Corps de requête HTML Form
import express from 'express'
import bodyParser from 'body-parser' // npm install body-parser
const app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.post('/login', (req, res) => {
const { username, password } = req.body
res.send(`Logged in as ${username}`)
})
<form method="POST" action="/login">
<input type="text" name="username" placeholder="Username">
<input type="password" name="password" placeholder="Password">
<button type="submit">Log In</button>
</form>
Paramètre de requête
// http://localhost/search?q=Foo
app.get('/search', (req, res) => {
const { q } = req.query
res.send(`Search query is: ${q}`) // Search query is: Foo
})
// http://localhost/search?tag=Foo&tag=Bar
app.get('/search', (req, res) => {
const { tag } = req.query // Array if several given
res.send(`Tags are: ${tag}`) // Tags are: ['Foo', 'Bar']
})
Paramètre de chemin
app.get('/users/:userId', (req, res) => {
const { userId } = req.params
res.send(`User ID is: ${userId}`)
})
app.get('/users/:userId/posts/:postId', (req, res) => {
const { userId, postId } = req.params
res.send(`User ID is: ${userId}, Post ID is: ${postId}`)
})
Base de données
Sequelize
- ORM (Object-Relational Mapping) pour Node.js permettant de faciliter l'interaction avec une BDD relationnelle en manipulant les données relationnelles sur des modèles objets
- Supporte MySQL, PostgreSQL, SQLite et autres
- Offre une interface programmatique pour interagir avec la BDD via les objets JavaScript, pour ne pas écrire de requêtes SQL
- Fonctionnalités avancées : pagination, associations, validation de données et migration de schéma de base de données
- Open source et communauté active
Usage
npm i sequelize sqlite3
const sequelize = new Sequelize('sqlite::memory:')
Modèle de données
import { DataTypes } from 'sequelize'
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
age: {
type: DataTypes.DECIMAL,
allowNull: false
},
})
CRUD
import { Op } from "sequelize"
// Create
await User.create({ username: "john", age: 42 }) // instance
// Read
const userById = await User.findByPk("de262606-070c-42e1-a43d-75c9696e919a") // instance | null
const userByName = await User.findOne({ where: { username: "alice" }) // instance | null
const users = await User.findAll({ where: { age: { [Op.gt]: 18 } } }) // array<instance>
// Update
await userByName.update({ age: 43 }) // instance
// Delete
await User.destroy({ where: { id: "de262606-070c-42e1-a43d-75c9696e919a" } }) // number
userById.destroy() // void
Validation
const User = sequelize.define('User', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [8, 20]
}
}
})
try {
User.create({
email: 'user@example.com',
password: 'password'
})
} catch(e) {
// e.errors
// e.errors.map(error => error.message)
}
Exercice
- Écrire le backend de l'exercice des TODO list avec Express et Sequelize
Association de modèles
import { DataTypes } from 'sequelize'
const Task = sequelize.define('Task', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
text: {
type: DataTypes.STRING,
allowNull: false
},
done: {
type: DataTypes.BOOLEAN,
allowNull: false
},
})
User.hasMany(Task, {
as: 'tasks',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Task.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
Projections et inclusions
const user = await User.findOne({
attributes: ['id', 'username'],
include: [{
model: Task,
as: 'tasks',
attributes: ['label'],
where: { done: false }
}]
})
{
id: '1f2fadb1-0d7e-47a7-9d5b-5d3e',
username: 'casear',
tasks: [
{ label: 'Vini' },
{ label: 'Vidi' },
{ label: 'Vici' }
]
}
User.hasMany(Task, {
as: 'tasks',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Task.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
Inclusions profondes
const user = await User.findOne({
attributes: ['username'],
include: [{
model: Collection,
as: 'collections',
include: [{
model: Task,
as: 'tasks',
attributes: ['label']
}]
}]
})
{
username: 'casear',
collections: [{
tasks: [
{ label: 'Vini' },
{ label: 'Vidi' },
{ label: 'Vici' }
]
}]
}
User.hasMany(Collection, {
as: 'collections',
foreignKey: 'userId',
onDelete: 'CASCADE'
})
Collection.belongsTo(User, {
as: 'owner',
foreignKey: 'userId'
})
Collection.hasMany(Task, {
as: 'tasks',
foreignKey: 'collectionId',
onDelete: 'CASCADE'
})
Task.belongsTo(Collection, {
as: 'collection',
foreignKey: 'collectionId'
})
JSON Web Token
JSON Web Token
- Format de jeton d'authentification compact et signé
- Basé sur une signature numérique (cryptographie asymétrique)
- Moyen d'authentification et d'autorisation des utilisateurs
- JWT contient des données signées, mais pas chiffrées
- Standard industriel RFC 7519
Notions de sécurité
Identification
Processus de reconnaissance d'un utilisateur ou d'une entité par le biais d'un nom d'utilisateur ou d'un identifiant unique.
Authentification
Processus de vérification de l'identité d'un utilisateur ou d'une entité.
Autorisation
Processus de détermination des droits d'accès d'un utilisateur ou d'une entité à des ressources spécifiques après qu'ils aient été identifiés et authentifiés.
Authentification et autorisation
Utilisateur
Authentification
Service d'authentification
Preuve (mot de passe, SMS, ...)
Token
Autorisation
Utilisateur
Service applicatif
Service d'authentification
Requête + Token
Token
Validation signature + droits
Réponse
Vérification
de non-révocation
Peut être
la même instance
Émission du jeton
Structure d'un JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
{
"alg": "HS256",
"typ": "JWT"
}
{
"sub": "123456789",
"name": "John Doe",
"iat": 1516239022
}
Hash(Secret,
Header + Payload)
Header
Payload
Signature
- Type de jeton et l'algorithme de hachage utilisé pour signer le jeton
- Informations à transmettre (identité utilisateur, autorisations, contexte, ...)
- Signature qui garantit l'intégrité des informations contenues dans le jeton.
Émission d'un JWT
import jwt from 'jsonwebtoken'
// Nous sommes libres sur le contenu du payload,
// mais il des pratiques normalisées
const data = {
jti: 'd6a0bf5d-bec1-4c85-852a-80cfd6ffbc74', // Numéro de série
exp: Math.floor(Date.now() / 1000) + (60 * 60), // Expire dans 1h
username: 'john.doe',
roles: ['ADMIN']
}
const token = jwt.sign(data, 'THE_SECRET')
Vérification d'un JWT
import jwt from 'jsonwebtoken'
try {
const data = jwt.verify(token, 'THE_SECRET');
// Vérifier que data.exp n'a pas expiré
// Vérifier que data.roles est suffisant pour l'action en cours
// Vérifier que data.jti est un numéro de série non revoquée
} catch(e) {
// Erreur de vérification
}
npm
Node package manager
- Gestionnaire de paquets pour Node.js
- Permet d'installer, de gérer et de partager des bibliothèques de code JavaScript réutilisables, appelées "packages" ou "modules"
- NPM est installé avec Node.js
- package.json pour lister les dépendances théoriques
- package-lock.json pour lister les dépendances effectives
- node_modules/ contient les dépendances installées
package.json
{
"name": "mon-projet",
"version": "1.0.0",
"description": "Un exemple de projet avec NPM",
"main": "index.js",
"scripts": {
"start": "node index.js",
"clean": "rm -rf node_modules"
},
"author": "John doe",
"license": "MIT",
"dependencies": {
"express": "^4.17.1",
"lodash": "^4.17.21"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
package-lock.json
{
"name": "mon-projet",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mon-projet/-/mon-projet-1.0.0.tgz",
"integrity": "sha512-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"dependencies": {
"express": "^4.17.1"
}
},
"express": {
"version": "4.17.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
"integrity": "sha512-mHJ9O79RqluphRrccoy1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
"requires": {
"body-parser": "1.19.0",
...
}
}
}
}
Initialiser un projet npm
# Créer un package.json originel pour votre projet
npm init
Ajouter une dépendance
# Ajoute une dépendance au package.json et l'installe dans node_modules/
npm install lodash
# Idem pour une dépendance de développement
npm install typescript --save-dev
Installer un projet
# Installe les node_modules à partir des dépendances du package.json
npm install
Utiliser une dépendance
import lodash from "lodash"
// Old way
const lodash = require("lodash")
Jouer un script
# Lance un script définit dans package.json
npm run start
Typescript
Optionnel
Javascript vs Typescript
Compile
JSDoc
Classes
Typing
Classes
Abstract classes
Interfaces
Generics
Decorators
Type inference
Inheritance
Inheritance
Enums
Type guards
Union types
Better auto-complete
Typescript
- Développé par Microsoft, première release en 2012
- "Javascript avec des types"
- Langage compilé en Javascript
- Super-set de Javascript = Tout code Javascript est valide lors de la compilation d'un fichier TS (Si les options du compilateur TS sont bien réglées)
- Ajoute des nouvelles fonctionnalités: Typing (alternative à JSDoc), Généricité, Interfaces, classes abstraite, enums etc...
-
Désavantages:
- Ajoute une étape (compilation) dans le processus de développement / déploiement
- Configuration du compilateur
Typing
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
function addition(a, b) {
return a + b;
}
function addition(a: number, b: number): number {
return a + b;
}
Typing
type InfosType = {firstname: string; company: {name: string; address: string}, age?: number}
let infos: InfosType = {
firstname: "John",
company: {
name: "Google",
address: "California"
},
};
/**
* @typedef {Object} InfosType
* @property {string} firstname
* @property {Object} company
* @property {string} company.name
* @property {string} company.address
* @property {number} [age]
*/
/** @type {InfosType} */
let infos = {
firstname: "John",
company: {
name: "Google",
address: "California"
},
};
Type inference, Union Type
function division(a: number, b: number) {
return b !== 0 ? a / b : 'wtf?';
}
const foo = division(1,2); // foo: number | 'wtf?'
const infos = {
firstname: "John",
company: {
name: "Google",
address: "California"
},
};
// infos: {firstname: string; company: {name: string; address: string}}
Enums
enum Direction {
Up,
Down,
Left,
Right
}
function movePlayer(direction: Direction) {
// ...
}
let oneDirection = Direction.Up; // oneDirection: Direction
movePlayer(oneDirection);
Classe, interface, inheritance, abstract
interface Animal {
name: string;
speak(): void;
}
abstract class AbstractAnimal implements Animal {
constructor(public name: string) {}
abstract speak(): void;
}
class Dog extends AbstractAnimal {
constructor(public name: string) {
super(name);
}
speak(): void {
console.log(`${this.name} says Woof!`);
}
}
Generics, Type guard
type NonNullType<T> = Exclude<T, null | undefined>
function filterNonNull<T>(array: T[]) {
return array.filter((item): item is NonNullType<T> => item !== null && item !== undefined);
}
const foo = [undefined, 1, 2, null, null, 42, undefined]; // foo: (undefined | null | number)[]
const bar = filterNonNull(foo); // bar: number[]
Decorators (Experimental)
function simpleDecorator(target: any, property: string, options: PropertyDescriptor) {
console.log("Decorating:", property);
}
class MyClass {
@simpleDecorator
greet() {
console.log("Hello!");
}
}
new MyClass().greet(); // Output: Decorating: greet\nHello!
Intégration dans Vue
<script setup lang="ts">
import { ref } from 'vue'
const message = ref("Hello world")
</script>
<template>
<h1>{{ message }}</h1>
</template>
<style scoped>
h1 {
text-decoration: underline;
}
</style>
Projet: Bid Bay
Lancement des tests
-
Génération du rapport:
cd frontend && npm run test-report
-
Interface Cypress:
cd frontend && npm run test
-
Lancer sous Chrome: Vérifiez que aucun tests ne passe suite au clonage du projet
-
A propos
- Projet en binôme
- Deadline: Dimanche 14 Avril
-
Notation
- Automatique via les tests E2E (18 pts)
- Qualité de code (2 pts)
Pour la techno, à vous de choisir !
branch main
branch main-ts
DS - Mardi 9 Avril 8h30
- Document autorisé (Une feuille A4 recto verso)
- Durée: 1h
- Partie 1: QCM (16 questions, 8 pts)
- Partie 2: Etude de cas (2 questions, 6 code a trou, 12 pts)
- Pas de Typescript
Javascript
By antho_loup
Javascript
- 454