c) Introduction au langage JS
Le JS s'écrit tant dans un browser que dans un environnement serveur.
Quand on écrit du JS au niveau client, on écrira du JS conforme au standard ECMAScript
.
Quand on écrit du JS au niveau serveur, on écrira du Node.js, du JS conforme au standard CommonJS
.
Il existe des différences lorsqu'on écrit du JS pour un browser ou lorsqu'on écrit du JS pour une application serveur. Néanmoins, il existe une syntaxe commune que nous allons voir dans ce chapitre.
Les instructions
👍 Même si ce n'est pas obligatoire, on recommande de séparer chaque instruction par un " ;
".
Cela permet d'augmenter la lisibilité du code.
jslet x = 1;console.log('x = ', x);
console.log()
: c'est une méthode qui permet d'afficher un message dans la console du browser ou de l'environnement Node.js. En Java, c'est l'équivalent de System.out.println()
en Java.
Les commentaires
On ajoute des commentaires dans son code via :
- "
//
" : une seule ligne commentée. - "
/*
" et "*/
" : un bloc de lignes commenté. - "
/**
" et "*/
" : un bloc de lignes commenté pour générer laJSDoc
d'une fonction.
js/*** JSDoc as comments* @param {message} message to be displayed in console*/function raiseAlert(message) {// Single line commentconsole.log(message); /* Regular commenton multiple lines*/console.log('An alert has been raised.');}
Les variables et constantes
Variable key sensitive
En JS, on ne déclare pas le type de variable, c'est un langage dynamiquement typé, c'est-à-dire que le type d'une variable est déterminé à l'exécution.
Les variables sont sensibles à la casse (ou "case sensitive") :
js// two different variableslet monBrowser;let monbrowser;
Variables locales
Pour définir une variable locale dont sa portée est associée à un bloc,
on utilise : let
.
jsif (true) {let blockScope = 'Hello';console.log(blockScope); // Hello}console.log(blockScope); // Uncaught ReferenceError: blockScope is not defined
Une variable let
[7.] :
- n'est pas accessible en dehors du bloc où elle est définie ;
- est processée à l'exécution seulement ;
- ne peut pas être redéclarée dans le même bloc.
constantes locales
Pour définir une constante dont sa portée est associée à un bloc,
on utilise : const
[8.].
jsif (true) {const constVar = 'Hello';console.log(constVar); // Helloconst SITE_URL = 'http://MyCMS.org';console.log(SITE_URL); // http://MyCMS.orgconstVar = 'Hi'; // Uncaught TypeError: Assignment to constant variable.console.log(constVar);}
Variables globales
Pour définir une variable globale si elle est déclarée en dehors d'une fonction, on utilise : var
.
jsif (true) {var globalVar = 'Hello';console.log(globalVar); // Hello}console.log(globalVar); // Hello
Une variable var
est:
- processée avant l'execution du code ("hoisting") ;
- accessible au travers de tout le programme ;
- redéclarable dans n’importe quel bloc.
Variables dont la portée est associée à une fonction
Pour définir une variable ou constante dont la portée est associée à une fonction, on peut utiliser : let
, const
ou var
.
jsfunction checkScopeVarInFunction() {var varInFunction = 'Hello';console.log(varInFunction); // Hello}checkScopeVarInFunction();console.log(varInFunction); // Uncaught ReferenceError: varInFunction is not defined
👍 Nous vous recommandons d'utiliser let
ou const
lorsque vous déclarer une variable ou constante au sein d'une fonction.
Assignation d'une valeur à une variable non déclarée
L'assignation d'une valeur à une variable non déclarée crée implicitement une variable globale.
jsfunction checkScopeVarInFunction() {varInFunction = 'Hello';console.log(varInFunction); // Hello}checkScopeVarInFunction();console.log(varInFunction); // Hello
👍 Nous vous recommandons de ne jamais utiliser de variables non déclarées.
Les dangers des variables globales
⚡ L'utilisation de variables globales peut s'avérer dangereux.
On crée une variable globale soit via var
, soit en initialisant une variable non déclarée.
jsvar index = 1;for (index; index <= 3; index++) {console.log(index); // 1 2 3}print();function print() {for (index; index <= 5; index++) {console.log('Print ' + index); // Print 4 Print 5}}
Dans cet exemple de code, on s'attendrait à ce que la deuxième boucle affiche :
Print 1 Print 2 Print 3 Print 4 Print 5
.
Recommandations concernant la portée de vos variables
⚡ L'utilisation de variables globales peut s'avérer dangereux quand on souhaiterait que la durée de vie de celle-ci corresponde à un bloc de code bien précis.
👍 Nous vous recommandons de définir des variables dont la portée est correctement exprimée par le mot-clé utilisé lors de la définition :
- utilisez
let
ouconst
pour des variables ou constantes disponibles au sein d'un bloc de code uniquement; - utilisez
var
(ouconst
) que si une variable (ou constante) doit être disponible au sein de tous les blocs d'un script. Néanmoins, même dans ce cas-ci, on préférera utiliser une variable vialet
.
Types de variables
Voici les types principaux de variables qui seront déterminés lors de l'initialisation d'une variable :
Number
(Nombre) : un seul type pour les entiers, réels, doubles…String
(Chaîne) : comprise entre guillemets simples, doubles ou entre ` et `.Bool
(Booléen).Array
(Tableau).Object
(Objet).
Afin de renvoyer sous forme d'une string le type d'une expression, on utilise l'opérateur typeof
.
jsconsole.log(typeof 12); // numberconsole.log(typeof 'I love JS'); // stringconsole.log(typeof true); // booleanconsole.log(typeof undeclaredVariable); // undefined
Les opérateurs
Opérateurs d'égalité ou de non égalité
Pour une comparaison stricte, sans conversion de type, on utilise ===
ou !==
.
Pour une comparaison avec conversion de type, on utilise ==
ou !=
.
js1 === 1; // true'1' === 1; // false1 == 1; // true'1' == 1; // true0 == false; // true0 == null; // falsevar object1 = { key: 'value' },object2 = { key: 'value' };object1 == object2; //false
👍 Nous recommandons en général d'utiliser l'égalité stricte sauf si vous souhaitez convertir le type des variables comparées.
Optional chaining operator : ?.
L'opérateur ?.
permet de lire la valeur d'une propriété d'un objet sans devoir contrôler si la référence de l'objet est valide.
Il agit comme l'opérateur .
sauf qu'au lieu de causer une erreur si une référence (d'une variable) est "nullish", c'est-à-dire null
ou undefined
, l'expression est court-circuitée et renvoie undefined
.
Sans test de condition, voici un morceau de code qui soulève une exception :
jsconst adventurer = {name: 'Alice',cat: {name: 'Dinah',},};const dogName = adventurer.dog.name; //Error: Cannot read properties of undefined (reading 'name')console.log(dogName);
En effet, la référence dog est undefined
.
Avant le récent "optional chaining operateur", voici ce que nous aurions pu écrire comme code avant d'initialiser dogName
:
jsconst adventurer = {name: 'Alice',cat: {name: 'Dinah',},};let dogName;if (adventurer !== undefined && adventurer.dog !== undefined)// often simplified to : if(adventurer && adventurer.dog)dogName = adventurer.dog.name;console.log(dogName); // undefined
Si la propriété d'un objet ne renvoyait jamais une valeur valide étant à false ou 0, alors on simplifiait souvent le check des valeurs nullish à qqch du style :
if(adventurer && adventurer.dog)
.
Maintenant, grâce à l'opérateur ?.
, nous pouvons écrire du code très concis et ne plus écrire de tests de conditions pour les valeurs de type "nullish" :
jsconst adventurer = {name: 'Alice',cat: {name: 'Dinah',},};const dogName = adventurer?.dog?.name;console.log(dogName); // undefinedconsole.log(adventurer?.someNonExistentMethod?.()); // undefinedconsole.log(adventurer?.kids?.[0]?.name); // undefinedconst kids = [{ name: 'John' }];let kids2;console.log(kids?.[0]?.name); // "John"console.log(kids2?.[0]?.name); // undefined
Nullish coalescing operator : ??
Le "nullish coalescing operator" permet de renvoyer ce qui se trouve à droite de l'opérateur quand ce qui se trouve à gauche est "nullish" (null
ou undefined
).
jsconst adventurer = {name: 'Alice',cat: {name: 'Dinah',},};const dogName = adventurer?.dog?.name ?? 'No dog';console.log(dogName); // "No dog"const catName = adventurer?.cat?.name ?? 'No cat';console.log(catName); // "Dinah"
Spread operator : ...
Spread operator au niveau d'un objet
Voici un exemple d'utilisation de spread operator
:
jsconst MENU = [{id: 1,title: '4 fromages',content: 'Gruyère, Sérac, Appenzel, Gorgonzola, Tomates',},{id: 2,title: 'Vegan',content: 'Tomates, Courgettes, Oignons, Aubergines, Poivrons',},{id: 3,title: 'Vegetarian',content: 'Mozarella, Tomates, Oignons, Poivrons, Champignons, Olives',},{id: 4,title: 'Alpage',content: 'Gruyère, Mozarella, Lardons, Tomates',},{id: 5,title: 'Diable',content: 'Tomates, Mozarella, Chorizo piquant, Jalapenos',},];const updatedPizza = {...MENU[foundIndex], ...req.body};
Le spread operator
est très utile en JS, ce sont les ...
.
Ici, nous créons un nouvel objet en intégrant deux objets : MENU[foundIndex]
et req.body
.
Le spread operator
permet de faire une "shallow copy", ou copie peu profonde, d'un élément de l'array MENU
, à l'aide de ...MENU[foundIndex]
.
Cela reprend toutes les propriétés de l'objet identifié par foundIndex
.
Ensuite, nous remplaçons les propriétés existantes de cette objet par les propriétés données dans req.body
à l'aide de ...req.body
. Bien sûr, si l'objet ...req.body
contient des nouvelles propriétés, celles-ci seront ajoutée à updatedPizza
.
Spread operator au niveau d'un array
Voici un exemple de spread operator
(voir les 3 petits points) :
jsorderedMenu = [...MENU].sort((a, b) => a.title.localeCompare(b.title));
Ici nous souhaitons faire un tri d'un MENU
(qui est un array) sur base d'une copie de celui-ci.
💭 Mais pourquoi le faire sur une copie ?
Si nous autorisons à trier l'array associé au MENU
, lorsque nous tenterons d'ajouter un nouvel élément, le dernier élément de l'array ne contiendra plus le dernier id (ou l'id le plus haut), car la fonction sort
modifie l'array sur lequel la méthode est appelée !
Nous souhaitons donc travailler sur une copie de cet array : [...MENU]
crée un nouveau tableau contenant tous les éléments du MENU
.
Spread operator en résumé
Le spread operator
est très puissant, il permet d'écrire du code concis et lisible.
Il est utilisé dans les objets, les arrays, ainsi que comme paramètre de fonction.
N'hésitez pas à consulter la documentation de MDN [R.57] pour en savoir plus.
Les autres opérateurs
Nous ne verrons pas tous les opérateurs logiques dans ce cours, ils sont sensés être connus.
Par exemple, les opérateurs logiques sont les habituels : &&
, ||
, !
Néanmoins, vous avez tous les opérateurs disponibles dans la documentation MDN ici [R.9].
Les conditions
Comme dans la majorité des langages, les instructions conditionnelles sont construites à l'aide de if
... else
.
Si vous souhaitez revoir les instructions conditionnelles, vous pouvez le faire dans la documentation MDN ici [R.10].
jslet isAuthenticated = false;if (isAuthenticated) {console.log('Render the HomePage.');console.log('You are authenticated.');} else {console.log('Render the Login Page.'); // Render the Login Page.console.log('You are not authenticated.'); // You are not authenticated.}
Voici un exemple de switch
:
jslet foo = 0;switch (foo) {case -1:console.log('negative 1');break;case 0: // foo is 0 so criteria met here so this block will runconsole.log(0);case 1: // no break statement in 'case 0:' so this case will run as wellconsole.log(1);break; // it encounters this break so will not continue into 'case 2:'case 2:console.log(2);break;default:console.log('default');}
Les fonctions
En JS, il existe plusieurs façon de déclarer des fonctions.
Les fonctions personnalisées
Ce sont des fonctions qui portent un nom, définies à l'aide du mot-clé function
.
jsfunction welcomeMessage(message) {return 'Message : ' + message;}let message = welcomeMessage('Welcome to everyone!');console.log(message); // Message : Welcome to everyone!
Les fonctions comme valeurs de variables
En JS, on peut assigner une fonction comme valeur de variable :
jsfunction welcomeMessage(message) {return 'Message : ' + message;}let x = welcomeMessage;message = x('Hi');console.log(message); // Message : Hi
Les fonctions anonymes
Ce sont des fonctions qui ne portent pas de nom.
jsconst welcome = function (message) {return 'Message : ' + message;};message = welcome('Hello world ; )');console.log(message); // Message : Hello world ; )
Si une fonction n'est jamais réutilisée et est très courte, il est parfois pratique de ne pas lui donner de nom.
Les fonctions "arrow"
Ce sont des fonctions qui ne portent pas de nom et qui s'écrivent sous une forme encore plus courte que les fonctions anonymes.
jsconst welcome2 = (message) => {return 'Message : ' + message;};message = welcome2('Hello world...');console.log(message); // Message : Hello world...// OTHER EXAMPLEconst higher = (n) => n + 1;console.log(higher(1)); // 2
On peut ne pas mettre les parenthèses si et seulement si la fonction ne possède qu'un seul paramètre.
On peut ne pas mettre les accolades si et seulement si le code est une expression, c'est-à-dire si la fonction renvoie quelque chose ; dans ce cas, on ne met pas de return
.
Les paramètres de fonctions
Les paramètres d'une fonction sont optionnels. Lors de l'appel d'une fonction, le valeur undefined
est allouée aux paramètres manquants.
Les paramètres ont une portée locale au sein de la fonction.
Le passage des paramètres se fait par valeur, sauf pour les objets où il se fait par référence.
Voici un exemple de passage d'un paramètre par valeur (la variable utilisée dans la fonction est un clone du paramètre qui a été passé à la fonction lors de l'appel) :
jslet myMessage = 'Hello';print(myMessage);function print(myMessage) {console.log(myMessage); // HellomyMessage = 'Good bye';}console.log(myMessage); // Hello
Voici un exemple de passage d'un paramètre, un objet, par référence (la variable utilisée dans la fonction n'est pas un clone du paramètre qui a été passé à la fonction lors de l'appel, c'est l'objet lui-même) :
jslet myMessage = { content: 'Hello' };consolePrint(myMessage);function consolePrint(myMessage) {console.log(myMessage.content); // HellomyMessage.content = 'Good bye';}console.log(myMessage.content); // Good bye
Les fonctions acceptent des paramètres optionnels avec une valeur par défaut qui est allouée lorsque ces paramètres sont omis lors de l'appel :
jslet welcome3 = function (message = 'HI DEAR HUMAN!') {return 'Message : ' + message;};message = welcome3();console.log(message); // Message : HI DEAR HUMAN
Autres caractéristiques des fonctions
Il existe encore d'autres caractéristiques pour les fonctions.
Par exemple, une fonction retourne une valeur à l'aide de return
.
Si vous souhaitez en savoir plus, vous pouvez lire la documentation MDN ici [R.11].
Les boucles
Les boucles permettent de répéter certaines tâches.
Si vous souhaitez en savoir plus sur l'utilisation de différents types de boucles en JS, vous pouvez le faire via la documentation MDN ici [R.12].
On utilise ces mots-clés pour définir des boucles en JS :
for
, for
/in
, for
/of
, while
, do
/while
.
Nous verrons aussi plus tard qu'il existe des fonctions sur des Array
permettant de boucler sur les éléments d'un tableau.
S'il y a un type de boucle à retenir, c'est la boucle for
, la plus classique, qui fonctionne dans tous les langages et avec la majorité des structures de données :
jsfor (let index = 0; index < 5; index++) {console.log(index); // 0 1 2 3 4}
Les tableaux
Introduction
Les tableaux sont des variables ou constantes de type Array
.
Un array (ou tableau) est un ensemble ordonné de valeurs auxquelles ont fait référence avec un nom et un indice.
Création de tableaux
jsconst LIBRARIES = ["Anime.js", "Three.js", "Phaser.io"];const emptyArray = [];const LIBRARIES_NOT_RECOMMENDED = new Array("Anime.js", "Three.js", "Phaser.io");const emptyArrayNotRecommended = new Array();const arr = new Array(10, 100) ; // What is the result ?const arr2 = new Array(10) ; // What is the result ?}
⚡ Dans l'exemple ci-dessus, arr
est un tableau de deux éléments contenant les valeurs 10
et 100
. Or arr2
est un tableau de `10 éléments ne contenant aucune valeur...
C'est assez déroutant et donc déconseillé de créer un tableau à l'aide du constructeur.
👍 Il est recommandé de créer un array à l'aide de []
.
Parcourir un tableau
On peut parcourir un tableau soit à l'aide des boucles classiques (for
& .length
), soit à l'aide de la méthode forEach()
.
jsfor (let index = 0; index < LIBRARIES.length; index++) {console.log(LIBRARIES[index]); // Anime.js Three.js Phaser.io}LIBRARIES.forEach((item, index) => console.log('[' + index + ']: ' + item));// [0]: Anime.js [1]: Three.js [2]: Phaser.ioLIBRARIES.forEach(function (item) {return console.log(item); // Anime.js Three.js Phaser.io});
Les tableaux multidimensionnels
Un tableau à deux dimensions, en JS, est un tableau dont tous ses éléments sont des tableaux.
jsconst numberOfRows = 2,const numberOfColumns = 2;const myTab = [];for (let x = 0; x < numberOfRows; x++) {myTab.push([]); // cleaner than : myTab[x] = [];for (let y = 0; y < numberOfColumns; y++) {myTab[x].push("[" + x + "][" + y + "]");// myTab[x][y] = "[" + x + "][" + y + "]"; not recommendedconsole.log(myTab[x][y]);}}
Autres méthodes associées aux tableaux
Il existe de nombreuses méthodes intéressantes associées aux tableaux, comme : pop()
, map()
, indexOf()
.
Si vous souhaitez approfondir ce sujet, vous pouvez consulter la MDN documentation ici [R.13].
Les template literals
On peut écrire des String sur plusieurs lignes à l'aide de ` `.
Il est possible d'inclure des expressions, généralement les valeurs de variables, dans des String en utilisant ${}
.
jsfor (let i = 0; i < LIBRARIES.length; i++) {htmlText += `<li class='list-group-item'>${LIBRARIES[i]}</li>`;}
Les objets
Introduction
En JS, tout est objet : les Strings, les tableaux, les API du browser, les objets personnalisés...
Cette partie ne donne que les bases de la gestion d'objets. Une autre partie du cours, appelée l'orienté objet en JS, fournit plus de détails.
Création d'objets
Introduction
Il existe plusieurs façons de créer un objet en JS.
Création d'un "object literal"
On peut créer un "object literal", qui est une liste de paires clé/valeur.
La clé sera le nom de la propriété d'un objet.
jslet raphael = {firstname: 'Raphael',lastname: 'Baroni',sayHello: () => 'Hi everyone !',};
Création d'objets vide
On crée un objet vide via .
⚡ Il n'est pas recommandé de créer un objet en utilisant le constructeur d' Object
via le mot clé new
.
jslet sandra = {};sandra.firstname = 'Sandra';sandra.lastname = 'Parisi';
Accéder aux propriétés d’un objet
Introduction
Il existe deux façons d'accéder aux propriétés d'un objet.
Accès à une propriété via un .
On accède régulièrement aux propriétés d'un objet via un .
:
jsconsole.log(raphael.firstname, ' :', raphael.sayHello()); // Raphael : Hi everyone !
Accès à une propriété via un ["object_key"]
On utilisera la clé, c'est-à-dire le nom de la propriété recherchée, sous forme de String, principalement dans les cas où l'on souhaite, lors de l'exécution, accéder dynamiquement à une propriété :
jsconsole.log(sandra['firstname'], ',', sandra['lastname']);// Sandra , Parisi
Les exceptions
Lancer une expression
Les exceptions permettent de gérer des cas d'erreur se produisant lors de l'exécution de vos scripts.
On lance une exception à l'aide du mot clé throw
et d'une expression.
On peut construire ses propres exception, en faisant un throw
de String, Number, Boolean ou Object.
jsfunction divideXByY(x, y) {if (y === 0) throw 'Division by 0 ! ';return x / y;}divideXByY(5, 0); // Uncaught Division par 0 !
On peut aussi lancer des exceptions à l'aide d'objets qui facilitent la gestion des exceptions : cela peut être l'objet Error
ou d'autres types d'erreurs prédéfinies par l'environnement JS (RangeError
, SyntaxError
...)
jsfunction RegularDivideXByY(x, y) {if (y === 0) throw new RangeError('Division by 0 ! ');return x / y;}try {RegularDivideXByY(5, 0);} catch (err) {console.log('RegularDivideXbyY():', err.name, ':', err.message);// RegularDivideXbyY() : RangeError: Division by 0 !}
Les deux propriétés intéressantes de "JS built-in error objects" sont : name
& message
.
Il est possible de créer vos propres classes d'erreur (en héritant de la classe Error
).
Si vous souhaitez plus de détails sur les "JS built-in error objects", vous pouvez parcourir la documentation MDN ici [R.14].
Intercepter une expression
On gère l'apparition d'une exception au sein d'un bloc de code à l'aide de try
... catch
et éventuellement finally
:
try{...}
: partie de code monitorée ;catch(err){...}
: instructions à exécuter en réponses à une exception ;finally{...}
: code à exécuter dans tous les cas après letry
...catch
, qu'il y ait eu une exception ou pas.
jslet result;try {result = RegularDivideXByY(5, 0);} catch (err) {console.log('RegularDivideXbyY():', err.name, ':', err.message); // RegularDivideXbyY() : RangeError: Division by 0 !} finally {console.log('RegularDivideXbyY() results:',result,"JS Divion's result",5 / 0);// RegularDivideXbyY() results: undefined JS Division's result Infinity}
Le destructuring assignment
Voici un exemple de destructing assignment
, une façon très concise en JS de créer des variables à partir de propriétés d'objets ou des valeurs d'arrays :
js// Here the variable and constants are definedconst decodedToken = jwt.verify(token, jwtSecret); // return the token payloadconst { username, iat, exp } = decodedToken;
Imaginez ici que la valeur de decodedToken
est : { username: 'admin', iat: 1661251095, exp: 1747651095 }
.
Ce code const { username, iat, exp } = decodedToken;
est l'équivalent de :
jsconst username = decodedToken.username;const iat = decodedToken.iat;const exp = decodedToken.exp;
Pour en savoir plus sur la puissance du destructuring assignement
, vous pouvez consulter la documentation de MDN [R.64].