Javascript

Formateur: Fabio Ginja

Les Variables

Variables - Déclaration

Une variable correspond à un espace mémoire de l'ordinateur dans lequel on stockera une valeur. Lors de la déclaration de la variable, on la nommera afin de faire référence à cet espace mémoire.

Les variables sont donc composées de deux éléments: un nom et une valeur.

Nom de la variable

Valeur/contenu de la variable

var name = "Fabio";
var isAwake = true;
var nbEcrans = 2;

Par convention, le nom d'une variable s'écrit en camelCase.

Variables - Primitive Types

Le javascript est un langage faiblement typé, et typé de façon dynamique.

Voyons d'abord les types primitifs:

var boolean = true;
var string = "Chaîne de caractères"; // Simple ou double quote
var number = 3.14; // Peut être un int ou un float
var notDefined; // La viariable est déclarée, mais non définie
typeof(notDefined); // undefined
var isNull = null;
typeof(isNull): // object
var symbol = Symbol(); // Symbole unique

On parle aussi de "scalar types".

var x = 3;
var y = x;
x = 5;
console.log(y) // affichera 3

La valeur de "y" ne change pas (elle ne devient pas 5). On stocke bien une valeur dans la variable.

Variables - Reference Types

Contrairement aux types primitifs les références ne stockent pas une valeur, mais une référence à un espace mémoire...
D'abord voyons les différents types:

var array = ["pomme", "fraise", "banane", 5, true, null]; // les tableaux
typeof(array); // array

var object = {
  nom: "Pierre",
  age: "25",
};
typeof(object); // object

var test = function() {
  console.log("Ceci est une fonction test");
}
typeof(test); // function

Use Strict

Il est possible de déclarer une variable "à la volée" (sans var) mais c'est une (très) mauvaise pratique car cela rend sa portée globale.

function test() {
  var name = "Fabio";
  number = 10; // 💩 DON'T DO THIS - La variable devient globale car initialisé sans "var"
}
test();
// Ici, on a accès à la variable "number", mais pas à "name"
var note = "fa";
var note = "do"; // 💩 Cela est possible mais à ne pas faire

Il est possible d'insérer "use strict"; en haut de son document, ce qui génère une erreur si on déclare des variables globales de façon accidentelle ou en cas de faute de frappe.

"use strict";
number = 15; // ❌ Error!
var note = "fa";
var note = "do"; // ❌ Error!

On peut également déclarer "use strict"; au sein d'une fonction:

name = "Fabio"; 💩 // DON'T DO THIS
function test() {
  "use strict";
  number = 10; // ❌ Error
}

ES6 - let, const

ECMAscript est un ensemble de normes concernant le javascript. La version 6 apporte son lot de nouveautés: on a désormais deux nouvelles façons de déclarer des variables.

const name = "Fabio";
name = "Batman"; // ❌ error: "name" is read-only
let day = "lundi";
day = "mardi"; // Ok

Lorsqu'on déclare une variable avec const, on précise que sa valeur ne va pas changer.

La différence entre let et var concerne la porté de la variable.

  • var est function-scoped
  • let et const  sont bloc-scoped

Scope - Portée

function fn() {
  if (true) {
    var isAlive = true;
  }
  console.log(isAlive); // renvoie true
}
function fn() {
  var isAlive;
  if (true) {
    isAlive = true;
  }
  console.log(isAlive);
}
fn(); // Ok

function fn() {
  // traitement...
}

La variable est remontée au haut du corp du script ou de la fonction. C'est également valable pour les fonctions:

Ces deux exemples sont équivalent. Cela est dû au hoisting (remontée de variable).

var est function-scoped, voici un exemple:

function block() {
  if (true) {
    let isAlive = true;
  }
  console.log(isAlive); // ❌ Error
}

En revanche, const et let n'ont que la porté du bloc où ils sont déclarés (ici, le bloc if).

Conclusion: ne plus utiliser var

Manipuler les objets

Un objet javascript se compose de deux éléments: une clé et une valeur.

const user = {
  name: "Pierre",
  age: "25",
};

Petite expérience:

const secondUser = user;
secondUser.name = "Marion";
console.log(user.name);

Ici on ne copie que la référence. "user" et "secondUser" pointent tous les deux vers le même objet/espace mémoire. Modifier ce qui se trouve à cette adresse mémoire impactera donc "user" et "secondUser".
Cela est également valable pour les tableaux et les fonctions.

Pour accéder à la propriété name de user, on utilisera la syntaxe suivante: 

nomObjet.nomPropriété;
/* Ou Encore */ nomObjet["nomPropriété"];

Manipuler les tableaux

Un tableau peut contenir différentes valeurs. Celles-ci ne sont pas obligées d'être du même type, on peut mettre ainsi dans un tableau:

  • string
  • number
  • array
  • object
  • function
  • etc...
array[0] // pomme
array[3] // 5

Pour accéder à un élément du tableau, il faut faire référence à son index:

Petite expérience:

const firstArray = ["pomme", "fraise", "banane", 5, true, null];
const secondArray = firstArray;
secondArray[0] = "cerise";
const myArray = ["pomme", "fraise", "banane", 5, true, null];
console.log(myArray.length); // 6 - Taille du tableau
myArray.push("new"); // Ajoute "new" à la fin du tableau
myArray.pop(); // Supprime le dernier élément du tableau

Que va t'il se passer?

ES6 - Template Literals

// ES5 - concaténation
const name = "Fabio";
console.log("Bonjour " + name);
// ES6 - String interpolation
const name = "Fabio";
console.log(`Bonjour ${name}`);

Les back quotes ont aussi l'avantage de garder les caractères spéciaux comme les sauts de ligne.

Une autre nouveauté d'ES6 permet de manipuler plus aisément les chaînes de caractères:

Pour écrire une chaîne de caractères, on peut donc utiliser les simple ('), double (") ou back (`) quotes.

 

Lorsque vous utiliser la back quotes, vous pouvez faire appels à une expression ou une variable javascript en utilisant la syntaxe suivante:

// ES6
const name = "Fabio";
console.log(`Bonjour ${name}. Il fait ${10 + 7}° dehors.
	Bonne journée.`);

Les Conditions

If, else

On va pouvoir vérifier la véracité d'une condition et exécuter du code en conséquence:

const nb = 0;
if (nb > 0) {
  // nb est positif
} else if (nb == 0) {
  // nb est nul
} else {
  // nb est négatif
}

Un if statement n'a pas besoin de else if ou de else, il peut être utilisé seul.

On remarquera que le double "==" n'est pas de l'affectation mais de la comparaison.

Un triple "===" serait une comparaison en valeur ET en type:

const nb = "5";
if (nb == 5) {
  // La condition est vraie, le code sera exécuté
}
if (nb === 5) {
  // La condition est fausse, le code ne sera pas exécuté
}

Switch - case

Lorsqu'on connaît les différents cas de figure que l'on veut tester, on peut utiliser un switch case:

const role = "admin";
switch (role) {
  case "admin":
    console.log("Welcome Admin")
    break;
  case "guest":
    console.log("You have a limited access.")
    break;
  default:
    console.log("You are not allowed here.")
    break;
}

Il est nécessaire d'ajouter le mot clé break pour éviter la lecture des autres conditions. Si jamais on fait un return à l'intérieur d'un case, ce dernier n'a en revanche pas besoin d'être suivi de break.

Expression Ternaire

Une ternaire est la forme la plus succincte pour vérifier une condition:

const hour = 13;
hour < 12 ? console.log("Matin") : console.log("Après-midi");
// condition ? si vrai : si faux;

Celle-ci est particulièrement utile pour vérifier une condition en une seule ligne. Elle est également performante en terme de temps de calcul.

Il est possible d'utiliser une ternaire à l'intérieur d'une ternaire (ternaire imbriquée). On évitera cependant cette utilisation car on perd en lisibilité.

// Ternaire imbriquée
const hour = 13;
hour < 12 ? 
  ( hour < 0 ? 
    console.log("Une heure doit être positive") :
    console.log("Matin")
  )
  : console.log("Après-midi");
// condition ? si vrai : si faux;

Opérateurs

Opérateurs de comparaison

&&  // ET logique
true && false // false
||  // OU logique
true || false // true
!   // NON logique
!true         //false

Opérateurs logiques

==  Égalité (en valeur)
=== Égalité (en valeur et en type)
!=  Inégalité (en valeur)
!== Inégalité (en valeur et en type)
>   Supériorité stricte
>=  Supériorité ou égalité
<   Infériorité stricte
<=  Infériorité ou égalité

Exercice

A vous de jouer!

1. Écrire une condition permettant de savoir si une variable est pair ou non et de l'afficher "pair" ou "impair" avec un if/else.
2. Avec un switch
3. Avec une ternaire

Les Fonctions

Écrire une fonction

Une fonction est un ensemble d'instructions. Une fonction peut prendre un, plusieurs, ou aucun paramètre. L'avantage d'une fonction est de pouvoir répéter les mêmes instructions à différentes reprises avec potentiellement des paramètres différents.

// Déclaration d'une fonction
function sayHello() {
  console.log("Hello!");
}

sayHello(); // Affiche "Hello!"

// Avec un paramètre
function sayHelloName(name) {
  console.log(`Hello ${name}!`);
}

sayHelloName("Fabio"); // Affiche "Hello Fabio!"

Une fonction peut aussi retourner quelque-chose:

function add(x, y) {
  return x + y;
}

Fonction Callback

Il est également possible de passer une fonction en paramètre d'une autre fonction:

// Déclaration d'une fonction
function sayHello() {
  console.log("Hello!");
}

sayHello(); // Affiche "Hello!"

// Avec un paramètre
function withCallback(callback) {
  // Traitement de la fonction...
  console.log("withCallback appelée");
  callback(); // on appelle callback après le traitement
}

withCallback(sayHello); // Affiche "withCallback appelée" puis "Hello!"

En javascript, on a très souvent recours à des callbacks.

IIFE

Une IIFE (Immediately Invoked Function Expression) est une fonction qui s'appelle elle-même. Cette dernière a une autre utilité: la portée de toute les variables à l'intérieur sera locale (par rapport à l'extérieur de la fonction.
Voici sa syntaxe:

// IIFE
(function () {
  // Traitement...
})();

Une IIFE peut également prendre un ou plusieurs paramètres.

// IIFE
(function (x, y) {
  console.log(x + y);
})(1, 2);
// Affichera 3

ES6 - Arrow Function

Une arrow function (fonction fléchée) permet d'avoir un syntaxe plus courte qu'une fonction classique. Cependant, une arrow function conserve toujours son contexte d'origine.

// Fonction classique - Etape 0
function square(x) {
  return x * x;
}

// Etape 1 - Je stock dans une variable une fonction anonyme
const square = function (x) {
  return x * x;
}

// Etape 2 - Je supprime le mot clé function et j'ajoute une flèche entre les paramètres et le corp de la fonction
const square = (x) => {
  return x * x;
}

// Etape 3 - Si jamais il n'y a qu'UN et UN SEUL paramètre, on peut supprimer les parenthèses de ce dernier:
const square = x => {
  return x * x;
}

ES6 - Arrow Function (2)

Attention cependant si vous faites un return implicite d'un objet:

// Etape 3 - Si jamais il n'y a qu'UN et UN SEUL paramètre, on peut supprimer les parenthèses de ce dernier:
const square = (x) => {
  return x * x;
}

// Etape 4 - Si la fonction n'a qu'une seule ligne de traitement, et que cette ligne est un return,
// alors on peut faire un return implicite en supprimant le mot clé return ainsi que les accolades du corps de la fonction.
const square = x => x * x;
function returnObject() {
  return {language: "Javascript"};
}
const returnObject = () => {language: "Javascript"}; // ❌
const returnObject = () => ({language: "Javascript"}); // ✅

Moins de code = moins d'erreurs

ES6 - Default Parameters

Il est possible, lors de la déclaration d'une fonction, de lui passer des paramètres qui auront une valeur par défaut:

let cart = 0;
function addToCart(quantity = 1) {
  cart += quantity;
}

addToCart();  // cart = 1
addToCart(4); // cart = 5

En ES5, on aurait dû écrire:

function addToCart(quantity) {
  if (quantity === undefined)
    quantity = 1;
  cart += quantity;
}

ES6 - Rest Parameters

On peut également créer une fonction qui accepte un nombre inconnu d'arguments:

const add = (...numbers) => {
  let result = 0;
  for (const number of numbers) {
    result += number;
  }
  return result;
}
add(): // 0
add(5, 10, 20); // 35
add(1, 4); //5

On peut avoir un argument nommé en début de liste suivi de rest parameters:

const add = (first, ...numbers) => {
  console.log(first);
  let result = 0;
  for (const number of numbers) {
    result += number;
  }
  return result;
}

add(5, 10, 20); // Affiche 5 - renvoie 30

ES6 - Spread operator

Le spread operator est un moyen de passer plusieurs arguments à une variable ou à une fonction:

const add = (...numbers) => {
  let result = 0;
  for (const number of numbers) {
    result += number;
  }
  return result;
}
const arrNumbers = [5, 10, 20];
add(...arrNumbers); // 35

Cette méthode peut également être utilisée pour copier un tableau ou un objet. Attention, cela n'effectue cependant qu'une copie superficielle de ce dernier (shallow copy).

const user = {
  role: "user",
  level: 1,
}

const secondUser = {
  ...user,
  role: "admin",
  points: 1000,
}
const info = ["admin", 1,];

const secondinfor = [...info, 1000,];

ES6 - le trailing comma n'est plus une erreur.

Le dernier élément d'un objet ou tableau peut avoir une virgule, c'est considéré comme une bonne pratique.

Exercice

A vous de jouer!

1. Créer une fonction qui affichera le paramètre dans la console.
	ex: log(10) -> Affiche "10" dans la console
	ex: log("Bonjour") -> Affiche "Bonjour" dans la console

2. Écrire une fonction qui prend en paramètre une température, et retourne un objet décrivant l'état de l'eau à cette température.
- On considère que l'eau est à des conditions normales de pression (gèle sous 0°, vapeur au-dessus de 100°)
	ex. h2o(-5) -> retourne {etat: "glace"}
	ex. h2o(5) -> retourne {etat: "liquide"}
	ex. h2o(150) -> retourne {etat: "vapeur"}

3. Écrire une fonction prenant 2 paramètres. Vérifier si le premier est multiple du second et renvoyer un booléen selon le cas.

4. Écrire une fonction qui donne le factoriel du nombre passé en paramètre.
- En math, factoriel de 5 est équivalent à 5 * 4 * 3 * 2 * 1. On l'écrit "5!". Vu autrement, la factoriel de 5 est égale à "5 * 4!"...
	ex: factoriel(5) -> retourne 120
    
5. Écrire une fonction qui retournera le résultat de la suite de Fibonacci du nombre passé en paramètre.
	ex: fibo(10) -> retourne 55

6. Afficher le résultat des fonctions précédentes dans la console grâce à votre fonction "log".

Les Opérateurs

Opérateurs

Voici les opérateurs qui permettent de réaliser des opérations:

let followers = 20; // '=' => affectation / assignation
followers = followers + 1; // '+' => incrémentation / addition
followers++; // version condensée de l'incrémentation (seulement +1)
++followers; // incrémente tout de suite la valeur
followers += 2; // version condensée de l'incrémentation (+2 ici)
followers = followers - 1; // '-' => décrémentation / soustraction
followers--;
followers -= 2;
followers = followers * 2; // '*' => multiplication
followers *= 2; // version condensée
followers = followers / 2; // '/' => division
followers /= 2; // version condensée
let modulo = 105 % 10; // '%' => modulo - vaudra 5, reste de la division
let puissance = 2**3 // '**' => puissance - vaudra 8 (2 * 2 * 2)

Les Boucles

While

Lorsqu'on a un traitement à répéter un certain nombre de fois, on utilise le principe des boucles. Voyons la boucle while:

let i = 10;
while (i >= 0) { // entre les parenthèse, la condition à vérifier
  console.log(i);
  i--; // on décrémente i sinon ou aurait une boucle infinie
}

Tant que la condition est vraie, le code à l'intérieur de la boucle sera exécuté. Il est donc primordial ne pas plus oublier la condition d'arrêt, sinon le code sera exécuté à l'infini, et faire crasher notre navigateur.

const fruits = ["pêche", "framboise", "mangue", "litchi"];

Exercice: Créer une boucle while qui affiche chaque élément du tableau

Exercice: Créer une boucle while qui affiche tout les multiples de 5 entre 1 et 50.

Do while

La boucle do while a la particularité de s'effectuer au moins une fois avant la vérification de la condition.

do {
  console.log("Tour de boucle");
} while (false);

Syntaxe:

Exercice: Code à trous

do {
  // Traitement
} while (condition);
let result = 0;
let i = 1;

do {
  // Traitement
} while (i <= 6);

console.log(result); // 21
// Si i <= 2 -> result = 3
// Si i <= 3 -> result = 6
// Si i <= 4 -> result = 10
// Si i <= 5 -> result = 15

For, for of

Syntaxe de la boucle for:

for (initialisation; condition; expressionFinale) {
  traitement;
}

for (let i = 1; i <= 10; i++) {
  console.log(i);
}

Exercice:

Parcourez et afficher chaque élément du tableau suivant à l'aide d'une boucle for:

const avengers = ["Iron man", "Captain America", "Thor", "Hulk", "Black Widow", "Hawkeye"];

Syntaxe de la boucle for of:

for (const hero of avengers) {
  console.log(hero);
}

Itérer dans un tableau

Méthode ES5: forEach

const cities = ["Tokyo", "Salvador", "Moscow", "Berlin", "Nairobi", "Rio", "Denver", "Helsinki", "Oslo"];

cities.forEach((city, index) => {
  console.log(`${city} est à l'index ${index}`);
});
const numbers = [1, 2, 3, 4, 5];

const doubleNumbers = numbers.map((number, index) => {
  console.log(`${number} est à l'index ${index}`);
  return number * 2;
}):
doubleNumbers; // [2, 4, 6, 8, 10]

La fonction forEach ne retourne rien.

Méthode ES6: map✔️

Cette méthode renvoie un nouveau tableau, celui d'origine n'est pas modifié.

Filter, reduce

La méthode filter crée et retourne un nouveau tableau contenant tous les éléments du tableau d'origine qui remplissent une condition déterminée par la fonction callback. (source MDN)

const cities = ["Tokyo", "Salvador", "Moscow", "Berlin", "Nairobi", "Rio", "Denver", "Helsinki", "Oslo"];

const result = cities.filter(city => city.length > 6);

console.log(result); // ["Salvador", "Nairobi", "Helsinki"]

La méthode reduce applique une fonction qui est un « accumulateur » et qui traite chaque valeur d'une liste (de la gauche vers la droite) afin de la réduire à une seule valeur. (source MDN)

const numbers = [1, 2, 3, 4, 5];

const doubleNumbers = numbers.reduce((accumulator, number) => {
  return accumulator + number;
}, 0);
console.log(doubleNumbers); // [2, 4, 6, 8, 10]

Some, every

La méthode some teste si au moins un élément du tableau passe le test implémenté par la fonction callback. Elle renvoie un booléen.

const cities = ["Tokyo", "Salvador", "Moscow", "Berlin", "Nairobi", "Rio", "Denver", "Helsinki", "Oslo"];

const result = cities.some(city => city.length > 6);

console.log(result); // true

La méthode every teste si tout les éléments du tableau vérifient une condition donnée.

const numbers = [1, 2, 3, 4, 5];

const supTo0 = numbers.every(number => number > 0);
const supTo2 = numbers.every(number => number > 2);

console.log(supTo0); // true
console.log(supTo2); // false

Find, findIndex, includes

La méthode find renvoie la valeur qui remplie la condition donnée par la fonction callback, ou undefined si aucun élément ne la remplie.

const numbers = [1, 2, 3, 4, 5];

const result = numbers.find(number => number > 6); // undefined
const result2 = numbers.find(number => number > 2); // 3

La méthode findIndex renvoie l'index de la valeur remplissant la condition donnée par la fonction callback, sinon elle renvoie -1.

const numbers = [1, 2, 3, 4, 5];

const result = numbers.findIndex(number => number > 6); // -1
const result2 = numbers.findIndex(number => number > 2); // 2

La méthode includes permet de déterminer si un tableau contient une valeur et renvoie true si c'est le cas, false sinon.

const avengers = ["Iron man", "Captain America", "Thor", "Hulk", "Black Widow", "Hawkeye"];

const result = avengers.includes(hero => hero === "Dr Strange"); // false
const result2 = avengers.includes(hero => hero === "Thor"); // true

Itérer dans un objet

Méthode ES5: for in

const character = {
  name: "Daenerys",
  house: "Targaryen",
  gender: "female",
}

for (var prop in character) {
  if (character.hasOwnProperty(prop)) {
    // Traitement
  }
}
Object.keys(character); // ["name", "house", "gender"]
Object.values(character); // ["Daenerys", "Targaryen", "female"]
Object.entries(character); // [["name", "Daenerys"], ["house", "Targaryen"], ["gender", "female"]]

⚠️Cette boucle peut causer des erreurs si on ne vérifie pas si la propriété appartient bien à l'objet et non au prototype.

Méthode ES6: ✔️ Transformer l'objet en tableau grâce à

  • Object.keys
  • Object.values
  • Object.entries

Exercice

A vous de jouer!

1. Écrire une fonction qui détermine si le nombre passé en paramètre est un nombre premier.
    
2. Écrire une fonction qui donne le factoriel du nombre passé en paramètre. Il faudra utiliser une boucle.
    
3. Écrire une fonction qui retournera le résultat de la suite de Fibonacci du nombre passé en paramètre. Il faudra utiliser une boucle.

4. Écrire une fonction prenant n paramètres. Vérifier si les n+1 paramètres sont multiples du premier et renvoyer un booléen selon le cas.

5. Vous avez un tableau d'objet qui contient la valeur des champs et un booléen pour déterminer si une erreur est présente dans ces champs (d'un formulaire).
- Renvoyer un seul message d'erreur si le champs contient une ou plusieurs erreurs.
- Sinon renvoyer "ok".
[
  {username: "test", error: false,},
  {email: "test@email", error: true,},
  {password: "a", error: true,},
]

6. Renvoyer un message d'erreur pour chaque champs du formulaire qui contient une erreur. Sinon renvoyer "ok".

Gestion d'erreur

Error()

Lorsqu'on écrit une fonction, il est possible de générer et renvoyer des erreurs. Pour générer une erreur il suffit d'écrire:

throw Error('Error message');

Le mot clé throw permet de lever une exception. Error() permet d'envoyer un message d'erreur indiquant le document dans lequel  l'erreur a eu lieu ainsi que sa ligne.

SyntaxError(); // erreur de syntaxe
TypeError(); // erreur de type
ReferenceError(); // déréférencement d'une référence invalide
RangeError(); // erreur quand une variable numérique ou un paramètre est en dehors de sa plage de validité

Error est générique, mais on a d'autre générateur d'erreurs:

Afin de gérer une erreur au sein de notre code, on peut utiliser l'instruction try catch.

Cette liste n'est pas exhaustive. Plus d'information sur le site MDN

try catch finally

try catch nous permet d'exécuter le code du bloc try. Si jamais une exception est levée dans le bloc try, on stoppe alors l’exécution du code de ce bloc pour passer au bloc catch. Dans tout les cas, on ira au bloc finally (mais écrire ce bloc est optionnel).

function isItFabio(name) {
  if (name !== 'Fabio') throw Error('This is not Fabio');
  console.log('Hello Fabio');
}

try {
  isItFabio('Nope');
} catch (err) {
  console.error(err);
} finally {
  console.log('finally called');
}

On remarquera que l'erreur est passée au bloc catch en paramètre, et que l'on l'affichera dans la console d'erreur (console.error).

Exercice

A vous de jouer!

1. Écrire une fonction qui additionne deux nombres.
Cette fonction devra gérer les cas d'erreurs (les renvoyer avec throw) et écrire un try catch afin de gérer les erreurs de façon sensible.
En fin de traitement, "bravo" doit être affiché dans la console.

Bind & this

this

En javascript, il est possible d'associer une méthode à un objet. Il est possible de le faire de plusieurs manières:

const person = {
  account: 10000,
  // avec le mot clé function
  work: function() {
    this.account += 1000;
  },
  // Ou en la déclarant directement
  printAccount() {
    console.log(this.account);
  }
}

person.work();
person.printAccount();

Afin de préciser de quel "account" on fait référence, on précise bien qu'il s'agit de la propriété de l'objet avec le mot clé this.

bind

Cependant, si jamais on affecte la méthode d'un objet à une variable, elle devient une fonction, et perd le contexte:

const person = {
  // ...
  work: function() {
    this.account += 1000;
  },
  // ...
}

const goWork = person.work; // ❌
goWork(); // undefined

Afin de lier la fonction "goWork" à l'objet "person", on utilisera bind:

const work = person.work.bind(person); // ✔️
work(); // Ok ✔️

bind (2)

Il est également possible de déclarer une fonction puis de la lier à un objet:

function showAge() {
  console.log(this.age)
}

const martin = {
  age: 18
}

const showMartinAge = showAge.bind(martin)
showMartinAge() // ✔️ 18

showAge() // ❌ undefined

On pourra remarquer qu'ici, il n'y a aucun ";". En javascript, on peut s'en passer sachant que le compilateur les mettra à notre place. Cela peut néanmoins provoquer certaines erreurs dans des cas particuliers (IIFE).

call, apply

La méthode apply() appelle une fonction en lui passant une valeur this et des arguments sous forme d'un tableau (ou d'un objet semblable à un tableau).

function showAge(firstname, lastname) {
  console.log(firstname, lastname, this.age)
}

const martin = {
  age: 18
}

showAge.apply(martin, ["Paul", "Dupont"]) // Paul Dupont 18

La méthode call() réalise un appel à une fonction avec une valeur this donnée et des arguments fournis individuellement.

function describePerson(firstname, lastname) {
  console.log(firstname, lastname, this.age)
}

const martin = {
  age: 18
}

describePerson.call(martin, "Paul", "Dupont") // Paul Dupont 18

Exercice

A vous de jouer!

1. Créer une fonction et la lier avec un objet. Cette fonction devra prendre au moins deux paramètres.
Cette fonction devra également utiliser le mot clé this.
  -ex: function fn(param1, param2)

Prototypes

Object.create()

Pour construire un objet avec des méthodes, et que ce dernier soit réutilisable, il est plus aisé d'utiliser Object.create():

const event = {
 getDate: function() {
    console.log(`${this.name} aura lieu en ${this.date}.`)
  }
}

const coupeDuMonde = Object.create(event)
coupeDuMonde.name = "Coupe du monde"
coupeDuMonde.date = 2022
coupeDuMonde.getDate() // Coupe du monde aura lieu en 2022.

⚠️Attention, si on modifie "getDate" dans event, la méthode sera modifiée immédiatement sur tout les objets.

coupeDuMonde.getDate() // Coupe du monde aura lieu en 2022.
event.getDate = () => console.log("Don't work anymore")
coupeDuMonde.getDate() // "Don't work anymore

new Object

On va d'abord créer une fonction (avec une lettre capitale) sur laquelle on pourra appliquer un constructeur. On pourra ajouter des méthodes à cet objet avec prototype puis "instancier" cet objet avec new.

function Event(name, date) {
  this.name = name;
  this.date = date;
}

Event.prototype.getDate = function() {
  console.log(`${this.name} aura lieu en ${this.date}.`)
}

const someEvent = new Event("Euro 2020", 2021)
someEvent.getDate() // Euro 2020 aura lieu en 2021.

Ou encore:

function Event(name, date) {
  this.name = name;
  this.date = date;
  this.getDate = () => {
    console.log(`${this.name} aura lieu en ${this.date}.`)
  }
}

Que fait new?

const prototype = {
  getDate() {
    console.log(`${this.name} aura lieu en ${this.date}.`);
  }
}
Object.setPrototypeOf(someEvent, prototype)

3. this est passé comme valeur à la fonction prototype.

someEvent.name = "Euro 2020"
someEvent.date = 2021

1. Il crée un nouvel objet.

2. Il lie cet objet à un autre objet en le définissant comme son prototype.

const someEvent = {}

4. Si la fonction ne renvoie pas d'objet, this est renvoyé

someEvent = {
  name: "Euro 2020",
  date: 2021,
  getDate() {console.log(`${this.name} aura lieu en ${this.date}.`);}
}

Exercice

A vous de jouer!

1. Créer un objet avec une méthode avec Object.create()

2. Créer un objet avec une méthode avec new

Classes

class

Les classes n'existent pas vraiment en javascript. Le mot clé class est simplement une nouvelle implémentation de la création d'objet tel que vue précédemment.

class Pokemon {
 constructor(name, type) {
    this.name = name
    this.type = type
  }

  useAttack(attack = 'Charge') {
    console.log(`${this.name} utilise ${attack} pour attaquer!`)
  }

  static staticMethod() {
    console.log('Métohde statique')
  }
}

On peut ensuite l'instancier comme suit:

const pikachu = new Pokemon('Pikachu', 'electrique')
pikachu.useAttack() // Pikachu utilise Charge pour attaquer!
Pokemon.staticMethod() // Métohde statique

extends

On peut également extend une classe à partir d'une autre. Voici comment faire:

class SuperPokemon extends Pokemon {
    constructor(name, type, specialMove) {
        super(name, type)
        this.specialMove = specialMove
    }

    useSpecial() {
        console.log(`${this.name} utilise son attaque spéciale: ${this.specialMove}`)
    }
}
const dracofeu = new SuperPokemon('Dracofeu', 'feu', 'Lance-Flammes')
dracofeu.useSpecial()

Exercice

A vous de jouer!

1. Créer une classe (ex: vehicule) et étendre (extend) cette classe à une autre (ex: moto)

Asynchrone

Promise

Une promesse est un objet qui représente la complétion ou l'échec d'une opération asynchrone.

Pour créer une promesse, il suffit d'utiliser le mot clé new et l'objet Promise():

const users = ["John", "Jessica"]

const getUsers = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve(users)
    } else {
      reject("Faillure")
    }
  }, 1000)
})

Question: Que verra t'on si l'on faisait un console.log de "getUsers"?

console.log(getUsers) // Promise { <pending> }

resolve() est invoqué si tout s'est bien passé pendant notre traitement, et reject() est invoqué en cas d'erreur.

then

Comment obtenir la réponse d'une promesse? Son traitement étant asynchrone, on a un moyen d’exécuter du code lors de la résolution de la promesse. Syntaxe:

promise.then(fonctionSiSucces, fonctionSiEchec)
getUsers.then(
  users => console.log(users[0]), 
  err => console.log("Error :(", err)
)

then est très utile, cependant on perd quelque peu en lisibilité lorsqu'on a des promesses chaînées (à la chaîne). Il est également possible d'utiliser catch afin de récupérer l'erreur.

getUsers
  .then(users => console.log(users[0])
  .catch(err => console.log("Error :(", err))

ES6 - async, await

Il existe également un autre moyen de rendre le code synchrone, s’exécutant ligne par ligne, même avec une promesse. Pour cela, il faut rendre la fonction asynchrone avec le mot clé async:

async function getFirstUser() {...}
// Ou encore
const getFirstUser = async () => {...}

Une fois cela fait, on peut maintenant utiliser le mot clé await dans le corps de la fonction afin de ne pas continuer la lecture du code temps que la promesse ne s'est pas résolue.

const getFirstUser = async () {
  try {
    let usersArray = await getUsers
    return console.log(usersArray[0])
  } catch (err) {
    return console.log("Error :(", err)
  }
}

fetch

Par définition, un appel à une base de donnée est asynchrone. On  ne sait pas si la requête va réussir ou échouer ni quand elle va se résoudre. Voyons comment faire un appel à une API avec fetch:

fetch(url, {options}) // Retourne une promesse

fetch('https://randomuser.me/api/')
  .then(response => response.json())
  .then(result => {
    console.log(result)
  })
  .catch(error => {
    console.log(error)
  })

Exercice: transformer ce code avec async await.

Javascript dans le web

Script

Pour exécuter du javascript, il faudra écrire une balise <script> dans notre html. On pourra écrire du javascript directement dans le corps de cette balise, ou alors faire référence à un fichier avec l'attribut src tout en précisant le type:

<script>
  console.log("Javascript dans le corp de script")
</script>
<script type="text/javascript" src="index.js"></script>

Or, le chargement de la balise script est "bloquant" et empêche le chargement des autres éléments de la page html. Pour résoudre ce soucis, on va préciser les attributs async (afin d'en faire le chargement de façon asynchrone) et defer (pour différer l'exécution du script à la fin du chargement de la page).

<script type="text/javascript" src="index.js" async defer></script>

Window

L'objet window est disponible lorsqu'on ouvre notre console dans notre navigateur. Il correspond à notre fenêtre.

// Quelques informations:
window.open() // Ouvre une nouvelle fenêtre.
window.close() // Ferme la fenêtre en cours.
window.prompt() // Ouvre la boîte de dialogue d'impression du document en cours.
window.alert() // Affiche une boîte de message d'alerte.
window.scroll() // Fait défiler la fenêtre à un endroit particulier dans le document.
window.scrollTo() // Fait défiler à un jeu de coordonnées particulier dans le document.
window.setInterval(() => console.log(5), 1000) // retourne un id
window.clearInterval(id) // prend en paramètre l'id de setInterval
window.setTimeout() // exécute la fonction après le timeout
window.close // détermine si la fenêtre est fermée
window.location // détermine sur quelle page on est (url)
window.scrollY // position de l'axe vertical du scroll
window.innerHeight // Hauteur de la fenêtre
window.innerWidth // Largeur de la fenêtre

Document

L'objet document est un sous objet de window , il ne correspond pas à la fenêtre mais bien à la page html.

// Sélection des éléments html
document.getElementById(id) // Retourne l'élèment possiédant l'id défini
document.getElementsByClassName(classe) // Retourne un array possédant la classe définie
document.getElementsByTagName(balise) // Retourne un array de la balise définie
document.getElementsByName(valeur) // Retourne un array des éléments ayant pour attribut name la valeur définie
document.querySelector(selecteur) // Retourne le premier élément qui correspond au sélécteur passé en paramètre (sélécteur css)
document.querySelectorAll() // Retourne un tableau
// Création d'un élément
document.createElement("div") // créer une balise div

Element

Voyons quelques propriétés utiles pout manipuler les éléments html.

// Sélection des éléments html
element.classname // Permet de modifier la classe de l'élément
element.classList // Retourne un array des classes de l'élément
element.attributes // Permet de récupérer les attributs
element.children // Retourne un array des enfants de l'élément
element.id // Retourne l'id de l'élément 
element.innerHTML // Permet de modifier l'html de l'élément
element.innerText // Permet de modifier le texte de l'élément
element.scrollTop // Définit ou obtient le nombre de pixels dont le contenu de l'élément a défilé vers le haut.

Exercice

A vous de jouer!

Javascript

By Fabio Ginja

Javascript

Slides de formation - Avril 2020

  • 1,258