Cours 1
Introduction à JavaScript un tour d'horizon

Généralités sur JavaScript

Bref historique

Années 1990 - Dynamic HTML – effets sur les pages web : Brendan EICH

  • Langage écrit en 1995 par Brendan EICH chez Netscape, pour associer des scripts à des éléments HTML.

  • Permet d’avoir des programmes en plus des pages Web dans le navigateur.
    Ces programmes permettent d’agir directement avec la page Web, sans rechargement.

  • Standard 96-97 (ECMAScript)
    actuellement version 14 (juin 2023),
    nouvelle version tous les ans

Bref historique

Années 2000 – Librairies évoluées :

  • Jquery, MooTools, AngularJS, … : proposent un ensemble de fonctions, ou même un cadre de travail complet pour JavaScript.
  • AJAX (utilisation « asynchrone » de JavaScript pour gérer des appels au serveur de données). Voir TD5 et suivants.

Années 2010 – Ère moderne :

  • Évolution de JavaScript : utilisation du langage côté serveur (retour aux origines).
  • Tendance actuelle : un seul langage dans la pile web, par exemple remplacer PHP par JavaScript.
  • Node.js pour des serveurs web écrits en JavaScript.

Node.js ~ PHP terminal CLI
Console navigateur ~ PHP lié au serveur Web Apache

Environnement de travail

Environnement d’exécution

  • la console des DevTools du navigateur :
    Endroit idéal pour tester le code, en interaction directe avec la page web. Outil indispensable. Les exemples du cours sont testés dans la console.

  • Node.js :
    Permet, entre autres, d’exécuter du JS dans un terminal.
    ⚠️ Non lié à une page Web

Environnement de développement

Nous vous conseillons WebStorm ou VSCode.

Caractéristiques générales

  1. langage qui dynamise les pages web côté client

    • Scripts interprétés dans le navigateur.
      Page web dynamique côté client (voir TD1), souvent par une gestion des événements (clics, …).
    • Différent des pages dynamiques côté serveur (PHP),
      où on gère des informations envoyées (formulaires, cookie) ou stockées dans la base de données, la session.
  2. langage interprété

    JavaScript est interprété au niveau du navigateur, sans la moindre compilation.

    L’exécution des scripts dépend de l’activation, côté client, de l’interpréteur JavaScript.

Variables en JavaScript

Les types principaux

  • JavaScript propose 8 types différents, nous en utiliserons essentiellement 4 :

    • Number (les nombres flottants double)
    • String (chaînes de caractères)
      Immuable (comme Java, Python, à l’inverse de C++, PHP)
    • Boolean (les booléens)
    • Object (tous les objets JavaScript)
      Dont les tableaux, stockés par référence (comme Java, …)
  • Les 4 autres types (BigInt, Null, Undefined et Symbol) sont pour nous moins communs.

  • Le typage JavaScript est :

    • Faible : Pas de type indiqué à la déclaration.
    • Dynamique : Le type d’une variable peut changer.

Déclaration des variables

  • Les variables se déclarent par les mots-clés var, let ou const.
  • La tendance actuelle est l’utilisation du mot-clé let. Nous privilégierons ce mot-clé.
let bianca = "Bianca Castafiore";
  • Le mot-clé const fonctionne comme let, à ceci près que la valeur ne peut pas être réaffectée après initialisation.
const pi = 3.141592653589793;
pi = 3;
// → TypeError: Assignment to constant variable.

Montrer la console de développement

Portée des variables

Variables locales

Une variable déclarée avec let a pour portée le bloc contenant (fonction, boucle for, …).

for(let i = 0; i < 2; i++) {
    console.log(i)
}
// → 0
// → 1
console.log(i)
// → Uncaught ReferenceError: i is not defined

Remarque :

  • JS permet des fois d’omettre le ; à la fin de l’instruction.
  • Bonne pratique : Toujours mettre ; à la fin.

Montrer node.js

Portée des variables

Variables locales

Une variable déclarée avec let a pour portée le bloc contenant (fonction, boucle for, …).

let i;
for(i = 0; i < 2; i++) {
    console.log(i);
}
// → 0
// → 1
console.log(i)
// → 2

Portée des variables

Variables locales

Une variable déclarée avec let a pour portée le bloc contenant (fonction, boucle for, …).

function f() {
    let j = 2;
    console.log(j);
}
f();
// → 2
console.log(j);
// → Uncaught ReferenceError: j is not defined

Portée des variables

Variables globales

Une variable déclarée dans la partie principale du script a donc pour portée tout le script : c’est une variable globale

let i_global = 0;

function f(nb) {
    i_global = nb;
}

f(8);
console.log(i_global);
// → 8

Opérateurs logiques

Entre variables de type Boolean

  • == / != : égalité en valeur ⚠️ Danger ! ⚠️
    Relation est symétrique, mais pas transitive en JS (ni en PHP)

      console.log([] == false); // → true
      console.log(false == "0"); // → true
      console.log("0" == []); // → false
    
  • === / !== : égalité en valeur et en type
    À privilégier.

      console.log([] === false); 
      // → false car types différents
      console.log(false === "0"); // → false
      console.log("0" === []); // → false
    

JavaScript est très permissif !

Conversions automatiques de type

console.log (8 * null);
// → 0

En effet, * est nécessairement la multiplication de deux nombres donc null est converti en un nombre

Number(null);
// → 0
// ATTENTION : çà ne marche que dans les cas simples
Number("five");
// → NaN
Number("5");
// → 5
Number(undefined);
// → NaN

Quizz 1/2

Question : Que rend le code suivant ?

console.log ("5" - 1);

Réponse : 4, car - est nécessairement la soustraction de deux nombres donc "5" est converti en un nombre

Quizz 2/2

Question : Que rend le code suivant ?

console.log ("5" + 1);

Réponse : "51" !
Il y a ambiguïté entre addition de nombres et concaténation de chaînes de caractères. L’opérateur le plus prioritaire en JavaScript est la concaténation.

Évaluation paresseuse de || et &&

Si expr1 est vrai, alors

  • (expr1 || expr2) est vrai
  • et expr2 n’est pas évalué

Si expr1 est faux, alors

  • (expr1 && expr2) est faux
  • et expr2 n’est pas évalué

Intérêt :

if (p !== null && p.nom == 'Eich')
// Si p est null, alors p.nom n'est pas évalué
// (donc pas de TypeError) 

(comme en PHP, Java, C++, … voir aussi le chainage optionnel ?. )

Considérez l’expression (expr1 || expr2)

Méthodes du type Number

let x = 3.141592653589793;
typeof(x); // → 'number'
x.__proto__;

donne ceci dans la console des outils de développement

Prototype methods

On sait alors que l’on peut appeler

x.toPrecision(4); // → '3.142'
x.toFixed(4); // → '3.1416'

Type String

Syntaxe entre guillemets simples 'coucou' ou "coucou" doubles :

  • même comportement, sauf échappement du délimiteur :
    • \'' si délimiteur simple
    • \"" si délimiteur double
  • saut de ligne avec \n, …

Syntaxe entre accent grave `coucou`. Permet le remplacement de variables avec ${...} :

let nom = 'Juste Leblanc';
let p = `<p> Bonjour ${nom} </p>`;
console.log(p);
// <p> Bonjour Juste Leblanc </p>

Méthodes du type String

let password = "@P9GXpXuF%sy";
password.length; // → 12
password.toUpperCase(); // → @P9GXPXUF%SY
password.charAt(6); // → 'X'
password.indexOf("%"); // → 9
password.split("X"); // → [ '@P9G', 'p', 'uF%sy' ]

(voir le __proto__ en détail !)

Tableaux en JavaScript

Déclarations possibles d’un tableau

Les tableaux JavaScript peuvent être déclarés par un appel à un constructeur de l’objet natif de JavaScript Array :

let tab = new Array("bonjour","salut","hello");

Mais il est plus simple de les déclarer par :

let tab = ["bonjour","salut","hello"];

Parcours d’un tableau

Boucle for classique :

let tab=["bonjour", "hello", "salut", "coucou"];

for (let i=0; i < tab.length; i++) {
    console.log(`mot n°${i} : ${tab[i]}`);
}
// mot n°0 : bonjour
// mot n°1 : hello
// mot n°2 : salut
// mot n°3 : coucou


Question : Comment JS évalue tab[i] alors que i est un flottant ?

Réponse : Il tronque i pour garder sa partie entière.

Parcours d’un tableau

Boucle for...of :

let tab=["bonjour", "hello", "salut", "coucou"];

for (let mot of tab) {
    console.log(mot);
}
// bonjour
// hello
// salut
// coucou

Méthodes des tableaux

// Insertion
let tab = [1];
tab.push(2); // En fin de tableau
tab.unshift(3); // En début de tableau
console.log(tab); // → [3, 1, 2]

// Supression
let fin = tab.pop(); // En fin de tableau
console.log(tab); // → [3, 1]
console.log(fin); // → 2
let debut = tab.shift() // En début de tableau

// Concaténation
let tabconcat = tab.concat([6,7]);
console.log(tabconcat); // → [ 3, 1, 6, 7 ]

Regarder le __proto__ pour d’autres méthodes intéressantes…

Fonctions

Syntaxe

Déclaration (comme en PHP)

function square(x) {
    return x * x;
};


Les variables peuvent stocker des fonctions !
Le code ci-dessus est équivalent à

let square = function (x) {
    return x * x;
};

Fonctions anonymes

On appelle fonction anonyme une déclaration de fonction sans nom.

Exemple :

setTimeout(
    function () { console.log("Boum"); },
    2000
);

Syntaxe raccourcie : Fonctions fléchées (arrow function en anglais)

setTimeout( () => {console.log(Boum);}, 2000);

Les 2 syntaxes suivantes sont équivalentes

const square1 = (x) => { return x * x; };
const square2 = x => x * x;

Fonctions

Les fonctions sont des objets de «première classe» : elles peuvent être manipulées et échangées comme tous les autres objets JavaScript.


Exemple : Mettons une valeur fonction dans une variable

function square(x) {
    return x * x;
};
// Affectation de la variable
let varfonc = square;
// Exécution de la fonction avec l'opérateur ()
varfonc(2);
// → 4

Fonctions

Une fonction peut prendre en argument une fonction

function boum() {
    console.log('Boum!');
}
// setTimeout execute la fonction boum après 2s
setTimeout(boum, 2000);


Une fonction peut aussi renvoyer une fonction.

Objets en JavaScript

Création de façon littérale

On peut définir un objet en donnant des paires clés-valeurs :

let p = {nom: "Haddock", prenom: "Archibald"};
p.nom; // → "Haddock

// p peut être complété
p.profession = "marin";
console.log(p);
// → { nom: 'Haddock', prenom: 'Archibald', profession: 'marin' }

Note : PHP permettait aussi d’ajouter des attributs :

$p = new stdClass();
$p->nom = "Haddock";
$p->prenom = "Archibald";

Création de façon littérale

On peut aussi créer l’objet avec des méthodes ou encore les ajouter après coup :

p.parler = function () {
    console.log("mille sabords !");
}
p.parler(); // → "mille sabords !"

Création selon un modèle

Façon plus classique de coder :

class Personne {
    constructor(nom, prenom, profession) {
        this.nom = nom;
        this.prenom = prenom;
        this.profession = profession;
    }

    parler() {
        console.log("mille sabords !");
    }
    
    static espece = "humain";
}
let capitaine = new Personne("Haddock", "Archibald", "marin");
capitaine.nom; // → "Haddock"
capitaine.parler(); // → "mille sabords !"
Personne.espece; // → "humain"

Langage basé sur les prototypes

  • JavaScript n’est pas basé sur les classes, mais sur les prototypes
  • Un prototype est comme une maquette de classe dynamique
    On peut modifier le prototype après coup :
    Personne.prototype.aurevoir = function() {
        console.log("Bon vent !");
    }
    
  • capitaine hérite dynamiquement des méthodes du prototype de Personne
    capitaine.aurevoir();
    // → "Bon vent !"
    

Fonctionnement des prototypes

Chaque objet se voit rattaché un prototype, qui est un objet.

typeof capitaine.__proto__ // → object

Donc le prototype a aussi son prototype, ce qui crée une chaîne de prototypes.

À la fin, on arrive au prototype Object dont le prototype est null.

capitaine.__proto__.__proto__.__proto__ // → null

Deux instances de la même classe ont le même prototype

let capitaine = new Personne("Haddock", "", "");
let tournesol = new Personne("Triffon", "", "");
capitaine.__proto__ === tournesol.__proto__;
// → true

Chaine prototypale ~ héritage (liste des classes mères)
ECMAScript 2015 Object.getPrototypeOf() équivalent à la propriété non-standard JavaScript __proto__

Fonctionnement des prototypes

Chaque prototype contient les méthodes de son type

Object.getOwnPropertyNames(capitaine)
// → ['nom', 'prenom', 'profession']
Object.getOwnPropertyNames(capitaine.__proto__)
// → ['constructor', 'parler']
Object.getOwnPropertyNames(capitaine.__proto__.__proto__)
// → ['constructor', 'hasOwnProperty', 'toString', ...]

Quand on tape capitaine.parler(), l’objet va chercher la méthode dans son prototype, sinon dans le prototype du prototype…

Du coup, capitaine hérite dynamiquement des méthodes du prototype de Personne.

capitaine.__proto__.aurevoir = function() {
    console.log("Bon vent !");
}
capitaine.aurevoir(); // → "Bon vent !"

Parcours des attributs / méthodes

On peut parcourir l’objet par une boucle for...in :

let gaston = {
    nom:"Lagaffe",
    prenom:"Gaston",
    profession:"gaffeur"
};
for(attribut in gaston) 
    console.log(`Gaston possède l'attribut ${attribut}`);
// Gaston possède l'attribut nom
// Gaston possède l'attribut prenom
// Gaston possède l'attribut profession

Tableaux associatifs ?

Les objets JavaScript peuvent être aussi vus comme des « tableaux associatifs » en lisant autrement leurs attributs :

let p = {nom:"Dupont",prenom:"Pierre",age:35};
p.nom; // → "Dupont"
p.prenom; // → "Pierre"
p.age; // → 35
p["nom"]; // → "Dupont"
p["pre" + "nom"]; // → "Pierre"
p["age"]; // → 35


Avantage de la syntaxe obj[expr] : expr est évalué