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

 

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.

 

Extrait de la Mozilla Developer Network

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

Projet Glitch

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

React

2013

218k ⭐

Angular

2010

93k

Vue

2014

206k

Svelte

2016

75k

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

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

 

https://github.com/Antloup/todo-list-server

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>

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