Programmation événementielle

Fonction anonyme

Objets de premier niveau

  • En Javascript, les fonctions sont des objets de premier niveau, ce qui signifie qu'ils peuvent être manipulés au même titre que, par exemple, les entiers, les tableaux ou les objets.

  • On peut par exemple

    • affecter une fonction à une variable ;

    • la passer en paramètre d'une autre fonction (par exemple addEventListener) ;

    • l'affecter comme attribut à un objet (ce qui est une manière de doter un objet de méthodes, comme en Java).

Fonctions anonymes

  • Javascript autorise la création de fonctions anonymes :

    function (param1, param2) { /* instructions here */ }
    
  • Contrairement à une déclaration de fonction, une fonction anonyme peut apparaître partout où une fonction est acceptée.

  • C'est utile dans les cas où cette fonction n'a pas vocation à être réutilisée ailleurs, en particulier pour les abonnements.

Exemple d'utilisation des fonctions anonymes

 1window.addEventListener("load", function() {
 2  document.getElementsByTagName('span')[0]
 3    .addEventListener("mouseover", function() {
 4      console.log("haha, tickles");
 5    }
 6  );
 7  document.getElementById('b1')
 8    .addEventListener("click", function() {
 9      console.log("click");
10    }
11  );
12});

Voir et modifier cet exemple.

Autre exemple d'utilisation de fonction anonyme

 1window.addEventListener("load", function() {
 2  const b1 = document.getElementById('b1');
 3
 4  b1.addEventListener("click", function() {
 5    b1.textContent = "(en attente)";
 6    b1.disabled = true;
 7    setTimeout(function() {
 8      alert("message");
 9      b1.disabled = false;
10      b1.textContent = "Afficher un message dans 2s";
11    }, 2000);
12  });
13});

Voir et modifier cet exemple.

Exécution asynchrone

Motivation

Dans l'exemple précédent, pourquoi utiliser la fonction setTimeout et non une fonction qui bloquerait l'exécution pendant 2s, comme dans l'exemple ci-dessous ?

// ⚠ MAUVAIS EXEMPLE ⚠
b1.textContent = "(en attente)";
b1.disabled = true;
sleep(2000); // fonction imaginaire (n'existe pas en JS)
alert("message");
b1.disabled = false;
b1.textContent = "Afficher un message dans 2s";

Avertissement

La fonction sleep n'existe pas réellement en Javascript, et pour cause puisque ce n'est pas la bonne manière de faire.

  • Réponse : parce que le navigateur n'utilise qu'un seul thread par page, dédié à la fois à l'affichage du HTML et à l'exécution du code Javascript.

  • Pendant qu'une fonction Javascript s'exécute, la page est donc totalement "gelée" (saisie, défilement...).

  • Si la fonction garde la main pendant une durée trop longue, ce phénomène sera perceptible, et dégradera l'expérience utilisateur.

Portée et fermeture

Portée (scope)

  • La portée d'une variable est la partie du code sur laquelle cette variable est définie.

  • Pour les variables globales, c'est l'ensemble des scripts exécutés par la page (y compris les scripts écrits par d'autres).

  • Pour une variable locale, avec les mots-clefs let ou const, c'est le bloc dans lequel elle est déclarée.

  • Pour une variable locale avec le mot-clef var, c'est la fonction dans laquelle elle est déclarée.

  • Une variable locale d'un bloc est donc accessible par les fonctions (anonymes ou non) définies à l'intérieur de ce bloc.

Fermeture (closure)

  • Une fonction porte avec elle le contexte dans lequel elle a été créée, c'est-à-dire l'état des variables locales qui lui sont accessibles.

  • Une telle fonction assortie d'un contexte est appelée une fermeture.

  • Dans l'exemple précédent, la variable b1 est définie au chargement de la page, mais « survit » à cette fonction, puisqu'elle est réutilisée au clic sur ce bouton, et encore deux secondes plus tard, dans le callback de la fonction setTimeout.

  • Visualisez l'exécution pas à pas d'un autre exemple de fermeture sur pythontutor.

Vocabulaire

Une fonction de callback est utilisée en paramètre d’une méthode ou d’une fonction qui l'exécute le moment voulu.

L'arbre DOM

Rappel

Les balises HTML décrivent une structure d'arbre.

graph { node [ shape=box, style=rounded] html -- head head -- title -- title_txt html -- body body -- h1 -- h1_txt body -- p p -- p_txt p -- a -- a_txt body -- img a [ label="a\nhref='./link'" ] img [ label="img\nsrc='./pic'" ] title_txt [ shape=box, style=filled, label="Le document" ] h1_txt [ shape=box, style=filled, label="Message important" ] p_txt [ shape=box, style=filled, label="Bonjour le " ] a_txt [ shape=box, style=filled, label="monde" ] }

Terminologie

  • Cet arbre s'appelle l'arbre DOM (pour Document Object Model).

  • Les nœuds correspondant aux balises sont appelés des éléments.

  • Les nœuds contenant le contenu textuel sont simplement appelés des « nœuds texte ».

Remarque

Il existe d'autres types de nœuds (par exemple les nœuds commentaire), mais ils sont plus rarement utiles.

Consultation du DOM

Récupérer des éléments depuis l'objet document :

getElementById, getElementsByTagName, getElementsByClassName, querySelector, querySelectorAll

Récupérer des éléments depuis un autre élément e :

e.getElementsByTagName, e.getElementsByClassName, e.querySelector, e.querySelectorAll, e.childNodes, e.children, e.parentNode,

Propriétés d'un nœud :

nodeName, nodeType, nodeValue, textContent

Modification du DOM

Création d'un nœud :

document.createElement, document.createTextNode, elt.cloneNode

Une fois créé, le nœud est encore hors de l'arborescence du document (et donc, non affiché). Il est nécessaire de le rattacher à un nœud parent par l'une des méthodes suivantes :

insertBefore, replaceChild, removeChild, appendChild,

Cheminement d'un événement

_images/event-flow.png

Source : http://www.w3.org/TR/DOM-Level-3-Events/#event-flow

Paramètre d'un listener

  • Par défaut, les listeners dont déclenchés à la remontée de l'événement.

  • Il est donc possible de s'abonner à tous les événements se produisant à l'intérieur d'un élément (et pas uniquement sur cet élément).

  • Le listener reçoit en paramètre un événement, dont l'attribut target contient l'élément cible.

Indication

On peut également forcer un listener à se déclencher à la descente (capture d'un événement) plutôt qu'à sa remontée (bubbling). Pour cela on passera true en troisième paramètre de addEventListener.

 1document.body
 2  .addEventListener("click", function(evt) {
 3    const msg = document.getElementById("msg");
 4    if (evt.target.tagName === "BUTTON") {
 5        msg.textContent = "Vous avez cliqué sur " +
 6           evt.target.textContent;
 7    } else {
 8        console.log(evt.target);
 9        msg.textContent = "Vous avez raté les boutons...";
10    }
11});

Voir et modifier cet exemple

Indication

L'objet événement passé en paramètre aux listeners a également une méthode preventDefault qui permet d'inhiber le comportement par défaut de l'événement.

Note sur les arguments des fonctions et Javascript

Comme on vient de le voir pour les listeners, Javascript est très tolérant avec le nombre de paramètres des fonctions.

  • Si on appelle une fonction avec trop de paramètres, les paramètres supplémentaires sont disponibles dans une variable spéciale nommée arguments (comparable à un tableau).

  • Si on appelle une fonction avec pas assez de paramètres, les paramètres oubliés reçoivent comme valeur undefined.

TP: Horloge avec alarme

Sujet

  • Créez une horloge digitale qui rafraîchit son affichage automatiquement. Vous aurez besoin pour cela de la classe Date et de la fonction setTimeout.

  • Vous ajouterez ensuite une fonctionnalité permettant d'ajouter, de modifier et de supprimer des alarmes. Pour faire sonner votre alarme, vous pourrez notamment créer un élément <audio>.

_images/tp-alarme.png

Pour aller plus loin

Loupe

http://latentflip.com/loupe/

Loupe vous permet de visualiser la manière dont les événements sont gérés en Javascript.

La vidéo qui sert d'introduction est également une bonne introduction aux mécanismes mis en œuvre.