Ordre d'exécution du navigateur

La programmation Asynchrone en Javascript cause bien des mots de têtes aux débutant d'après le nombre élevé de questions qui sont demandées sur irc.freenode.org ##javascript . Et pourtant, le langage Javascript a toujours supporté les événements dans les navigateurs. On peut aussi parler de programmation événementiel.

Tout d'abord, il faut arrêter de penser séquentiellement en terme de ligne de code et commencer à réfléchir selon le temps d'exécution du code. La ligne 1 peut être exécutée après la ligne 2 selon l'ordre temporel. Parfois l'ordre du code est important et parfois il ne l'est pas.

Tout d'abord, lorsqu'on veut modifier un élément du document (DOM), on doit le faire "après" qu'il soit créé (dans le temps). L'analyse (parsing) du document se fait selon l'imbrication des balises XHTML. L'élément existe après sa balise de fermeture. La première technique séquentielle consiste à placer notre <script> après la balise de fin. Exemple:

<div id="mondiv"> ...  </div>

<script>
/* La balise script est après la balise div. */
document.getElementById('mondiv')
</script>

On ne pourrait pas exécuter la ligne document.getElementById avant la balise div ou à l'intérieur de la balise div. Voici une balise script mal placée pour utiliser #mondiv.

<div id="mondiv"> 
   <script>
   /* La balise div n'est pas terminée, on ne peut pas la sélectionner . */
   /* si on utilise getElementById, le navigateur retourne null */
   </script>
</div>

La deuxième façon d'exécuter du code "après" (dans le temps) est de profiter des évènements du navigateur. Qui dit évènement dit fonction de rappel (callback). Le navigateur nous permet de lancer des fonctions (les fonctions de rappel) lorsqu'un certain évènement survient. Il peut s'agir d'un clic de souris, du chargement de la page ou d'un évènement temporel après 10 secondes. Il faut consulter la documentation javascript et DOM pour connaître la liste des évènements. Un des évènements les plus utilisés est le type "load" de l'objet window. Lorsque le navigateur a terminé de charger le code HTML, le code javascript, les fichiers CSS et les images, il émet le signal "load". C'est la tâche du programmeur d'intercepter ce signal ou cet évènement avec une fonction qu'il définit.

1  <script>
2  function chargement() {
3     document.getElementById('mondiv')
4  }
5  window.addEventListener('load', chargement);
6  </script>

La fonction "chargement" sera appelée seulement lorsque le navigateur émettra le signal "load" (la fin du chargement du document et des ressources). Voici ce que le navigateur fait ligne par ligne:

  • À la ligne 1, le navigateur lit une balise de début <script> et commence l'exécution du code javascript.
  • À la ligne 2, le navigateur défini une fonction globale "chargement".
  • À la ligne 5, le navigateur ajoute la fonction de rappel "chargement" à l'objet window pour le type d'évènement "load". Il s'agit d'une association entre l'objet, le type d'évènement et la fonction. L'association est stockée dans la mémoire du navigateur.
  • À la ligne 6, le navigateur lit la balise de fin </script> et arrête l'exécution en mode "javascript".
  • Ensuite. Le navigateur charge le document en entier et les ressources associées.
  • Lorsque tout est chargé, l'évènement "load" est émis. Le navigateur exécute chaque fonction de rappel spécifique à l'évènement "load" et lance la fonction chargement.
  • L'instruction de la ligne 3 est exécutée.

Javascript possède aussi des évènements qui sont émis après une période de temps avec la fonction setTimeout et setInterval. Voici un code et l'ordre d'exécution de deux évènements temporels:

1  function allo() {
2     console.log('allo');
3  }
4  function toi() {
5     console.log('toi');
6  }
7  setTimeout(allo, 1000);
8  toi();
9  setTimeout(toi, 3000);
  • À la ligne 1, le navigateur défini une fonction globale "allo".
  • À la ligne 4, le navigateur défini une fonction globale "toi".
  • À la ligne 7, le navigateur associe une fonction à un évènement temporel qui se produira dans 1 seconde (1000 ms).
  • À la ligne 8, le navigateur exécute la fonction toi() - ligne 5. La console affiche "toi".
  • À la ligne 9, le navigateur associe une fonction à un évènement temporel qui se produira dans 3 secondes (3000 ms).
  • Après 1 seconde, la fonction allo() est exécutée - ligne 2. La consolle affiche "allo".
  • Après 3 seconde, la fonction toi() est exécutée - ligne 5. La consolle affiche "toi".

Résultat dans la console: toi allo toi

Exécutions des lignes: 1 (déclaration), 4 (déclaration), 7, 8, 5, 9, 2, 5.

Évènements inclus dans le code HTML

Accès à la variable this

En HTML : À l'intérieur d'un attribut-évènement dans le code HTML d'un élément, la variable "this" correspond à l'élément.

<a onclick="console.log(this)">test</a>

Accès à la variable event

En HTML : À l'intérieur d'un attribut-évènement dans le code HTML d'un élément, la variable "event" correspond à l'évènement.

<a onclick="console.log(event)">test</a>

L'accès aux autres champs du formulaire

En HTML5 : À l'intérieur d'un attribut-évènement dans le code HTML d'un champ de formulaire, les champs peuvent être accéder en utilisant leur nom (name) ou id. Les noms/ids seront prioritaire sur les variables (ainsi que les fonctions) définies globalement.

Soit le HTML suivant:

<form>
<input id="a" />
<input name="b" />
<input type="button" id="c" onclick="console.log(a, b, c)" />
</form>

Résultat lorsqu'on clic sur le troisième champ: les trois champs.

Pour plus de compatibilité, on peut aussi utilisé this.form.elements['inputname']

Voir les tests: automatic_id

L'accès à la propriété onclick

Soit le HTML suivant:

<a href="#" onclick="return doSomething(this,1)">test</a>

Et le Javascript suivant:

var a = document.links[0];
console.log(typeof a.onclick, ":", a.onclick);
console.log(typeof a.getAttribute('onclick'), ":", a.getAttribute('onclick'));

Résultats dans iceweasel 10.0.12:

function : function onclick(event) {return doSomething(this, 1);}
string : return doSomething(this,1)

Résultats dans Internet Explorer 9 (mode standard IE9):

function : function onclick(event) 
{
return doSomething(this, 1)
}
string : return doSomething(this,1)

Résultats dans Internet Explorer 8 (mode standard IE8):

function:function onclick()
{
return doSomething(this, 1)
}
string:return doSomething(this,1)

Résultats dans Internet Explorer 8 (mode IE7 et Quirk):

function:function onclick()
{
return doSomething(this, 1)
}
function:function onclick()
{
return doSomething(this, 1)
}

Voir aussi: Scope changes during HTML parsing

6 façons d'appeler une fonction où "this" est l'élément HTML

/* notre fonction qui affiche le id */
function f() {
    console.log(this.id); /* affiche buttonA à buttonF */
}

/* avec onclick/click, on aurait pu exécuter l'intérieur de la fonction après la déclaration des inputs HTML */
window.addEventListener('load', function() {

  document.getElementById('buttonE').onclick = f;
  document.getElementById('buttonF').addEventListener('click', f, false);

},false);

<input type="button" onclick="f.bind(this)()" id="buttonA" value="click A"/>
<input type="button" onclick="this.f = f; this.f();" id="buttonB" value="click B" />
<input type="button" onclick="f.call(this)" id="buttonC" value="click C"/>
<input type="button" onclick="f.apply(this)" id="buttonD" value="click D"/>
<input type="button" id="buttonE" value="click E"/>
<input type="button" id="buttonF" value="click F"/>

Voir le test sur l'association de "this" dans les évènements