version 2.3.0, dernière mise à jour le 21 novembre 2018.
Il est courant de rencontrer sur Internet des codes JavaScript
écrits par d'autres programmeurs, et que l'on souhaite incorporer à ses pages. Il arrive aussi que l'on souhaite réutiliser du contenu, des fonctions que l'on a soi-même écrites précédemment.
Cependant, ce désir de vouloir reprendre du code de sources externes se heurte souvent à des difficultés :
le code est incompatible avec certains navigateurs ;
les noms des fonctions entrent en compétition. Par exemple, il est courant d'appeler au chargement de la page une fonction que l'on nomme Init()
ou Chargement()
; mais ces noms sont très communs et si l'on souhaite mélanger des codes issus de plusieurs sources, il est probable que des erreurs soient créées ;
les variables elles-mêmes peuvent porter des noms ambigus. Cela est plus grave, dans la mesure où la redéfinition d'une variable n'est pas signalée comme un bogue du code, et peut donc passer inaperçue ;
les gestionnaires d'événement peuvent s'écraser l'un l'autre.
Heureusement, de « bonnes pratiques » de codage permettent de ne pas tomber dans ces pièges.
Nous avons vu dans un chapitre précédent comment ajouter du contenu à un document. Cependant, cette possibilité présente de gros inconvénients en termes d'accessibilité : les utilisateurs ne disposant pas de JavaScript
sont dans l'incapacité de consulter ce contenu si des précautions ne sont pas prises.
À cet égard, il est primordial que tout contenu informatif présent dans un document doit être présent dans le code HTML. Par exemple, il ne faut pas utiliser JavaScript
pour ajouter, par exemple, un menu de navigation.
Une alternative consiste à faire en sorte que le contenu soit déjà présent dans la page, puis, au chargement du document, le cacher par JavaScript
pour ensuite le faire réapparaître en fonction des besoins.
CSS
est un langage qui est dédié à la mise en forme. Par conséquent, il est toujours plus rapide de changer l'apparence d'un élément en faisant appel à CSS qu'à JavaScript
. Cela implique de préférer systématiquement l'utilisation de la propriété className
ou classList
aux propriétés de l'objet style
. Par exemple, si l'on doit changer en rouge la couleur d'un élément elt
, on définira de préférence une classe "exemple"
qui spécifiera une couleur de police rouge et on fera elt.className="exemple";
ou, mieux, elt.classList.add("exemple");
au lieu d'indiquer elt.style.color="red"
. Cela offre en outre l'avantage d'externaliser en-dehors du script la définition de cette mise en forme.
Attention, certains éléments sont déjà pourvus d'une classe dans le code HTML
. Manipuler la propriété className
peut faire perdre une mise en forme. Rappelons en effet qu'il est tout à fait possible pour un élément d'avoir plusieurs classes : <span id="span1" class="classe1 classe2 classe3">(…)</span>
. Si on utilise directement span1.className="classe3"
, par exemple, l'élément va perdre toutes ses autres classes, ce qui n'est pas forcément le but recherché ; il peut aussi arriver que l'élément possède déjà la classe que l'on souhaite ajouter.
Avant de faire appel à une propriété ou une méthode, il vaut mieux au prélable s'assurer qu'aucune erreur ne sera produite si elle n'est pas disponible. Il était d'usage de faire une détection du navigateur utilisé, mais cette pratique a disparu pour laisser place à des méthodes plus efficaces.
Il suffit en effet de tester si ce dont on a besoin ne produit pas de code erreur, et de conditionner la poursuite de l'exécution du code au fait qu'il n'y a pas d'erreur.
Supposons par exemple que l'on souhaite utiliser la méthode getElementById
. On écrira…
if (!document.getElementById) return ;
//Code à exécuter quand getElementById est supporté.
Ainsi, le navigateur ignorera « naturellement » le code qu'il ne peut pas comprendre.
Avant d'agir sur un élément, encore faut-il être sûr qu'il existe… Cela peut paraître superflu quand on n'intervient que sur une seule page, mais lorsque le fichier JavaScript
est amené à être mis en commun entre plusieurs documents, il est possible que le cas apparaisse où un élément n'existe pas sur une page… ce qui peut amener des erreurs.
Selon le cas, on peut alors soit placer les instructions réservées à un élément à l'intérieur d'un test, soit stopper l'exécution du script :
if (document.getElementById("id1"){
//Code à exécuter quand l'élément d'identifiant id1 existe.
}
if (!document.getElementById("id1") return ;
{
//Code à exécuter quand l'élément d'identifiant id1 existe.
}
Le mode strict est un mode de fonctionnement de JavaScript
où l'interpréteur est moins tolérant à des imprécisions de syntaxe. Nativement en effet, de même qu'en HTML
les navigateurs mettent en œuvre des stratégies diverses pour résoudre les cas où le code n'est pas strictement conforme à la DTD, les interpréteurs JavaScript
passent sous silence un certain nombre d'avertissements. Un inconvénient majeur est que cette mansuétude alourdit le temps d'analyse du script ; passer en mode strict entraîne donc des gains de performance. De plus, ce mode interdit l'usage de mots-clefs qui sont anticipés pour les futurs développements d'ECMAScript
.
On invoque le mode strict en ajoutant…
"use strict" ;
Cette ligne doit être ajoutée en tout début de script si l'on souhaite que ce mode s'applique globalement, ou bien avant quelque instruction que ce soit au début d'une fonction. Il y a cependant un inconvénient ; il est certes possible de passer en mode strict progressivement, fonction après fonction mais si on concatène le code total et si on commence par du code strict, alors l'ensemble du code concaténé sera considéré comme strict, ce qui peut engender nombre d'erreurs. De même, concaténer un code strict avec du non strict en commençant par du non strict fait que l'ensemble du code concaténé sera en non strict, et on perd les avantages du mode strict.
Un certain nombre d'erreurs silencieuses en non strict deviennent explicites en mode strict, et lèvent des exceptions. Par exemple, si on écrit dans une fonction…
'use strict' ;
maVariable = 42 ;
… alors cela générera une erreur. En effet, le mode strict rend impossible la création par inadvertance d'une variable globale. Nous aurons l'occasion d'y revenir, mais créer une variable globale ajoute en fait une propriété à l'objet global de la page, à savoir window
. Si l'on veut déclarer une variable globale, il faut le faire explicitement, en écrivant par exemple ici window.maVariable = 42;
Le mode strict détecte aussi une erreur de syntaxe quand dans la déclaration (pas l'appel, où cela est autorisé) d'une fonction, des paramètres sont passés en double, alors que cette erreur, en mode non strict, le dernier argument dupliqué cache les précédents portant le même nom :
maFonction = function(x, y, y){
"use strict" ;
return x+y+z ;
}
Le code précédent déclenche une erreur dès l'analyse par l'interpréteur, mais uniquement lors de son exécution si on revient en mode non strict.
En mode strict également, il n'est pas possible de modifier par inadvertance la valeur d'une variable externe ou globale lors de l'appel à une fonction eval()
:
var x1 = "hop" ;
var x2 = "hop" ;
var ynonstrict = eval(var x1 = 'hophop'; x1) ;
var ystrict = eval('use strict'; var x2 = 'hophop'; x2) ;
console.log("x1 = "+x1) ;
console.log("x2 = "+x2) ;
console.log(ynonstrict) ;
console.log(ystrict) ;
À l'issue de ce code, ynonstrict et ystrict valent "hophop", x1 vaut aussi "hophop" car sa valeur a été modifiée lors de l'évaluation de ynonstrict, mais x2 vaut encore "hop" car l'usage du mode strict lors de l'évaluation de ystrict en a empêché la modification.
Enfin, en mode strict, il est interdit de déclarer une fonction ailleurs qu'au niveau le plus haut d'un script ou d'une autre fonction. Par exemple, il est interdit de déclarer une fonction à l'intérieur d'une boucle for
ou d'un test if
.
Il existe deux grandes « écoles » de placement de l'élément script
:
soit dans l'entête du document HTML. Cette présente l'inconvénient majeur, appliquée sans précaution, de différer le chargement du DOM de la page, et donc son affichage, au chargement de l'intégralité du script. C'est la solution retenue pour les corrections d'exercices de ce cours, simplement pour des raisons pratiques de correction et de tutorat en présentiel.
soit à la toute fin du code, juste avant le </body>
. Ainsi, le code HTML est restitué par le navigateur avant de charger le script.
Les attributs defer
et async
permettent de gérer l'ordre de chargement des scripts.
defer
permet d'indiquer explicitement au navigateur qu'il doit charger les scripts en parallèle, mais tont en conservant l'ordre dans lequel les éléments script
sont introduits dans le code source. Cet attribut n'a que peu d'utilité à l'heure actuelle, dans la mesure où ce comportement de téléchargement en parallèle est déjà implémenté nativement dans les navigateurs récents.
async
permet de charger les scripts en parallèle, et n'entrave pas la restitution du DOM par le navigateur. Cet attribut est donc à déconseiller si un script dépend du chargement d'un précédent pour pouvoir s'exécuter convenablement, ou si le JavaScript modifie de manière substantielle le DOM, au point d'en compromettre la compréhension en cas de chargement partiel.
Ces attributs peuvent maintenant être avantageusement remplacés par les « promesses » JavaScript
, sur lesquelles nous reviendrons.
Nous avons jusqu'à présent chargé nos fonctions Init
à l'aide de l'attribut onload
de l'élément body
. Cependant, il y a fort à parier que si l'on fait appel à d'autres scripts, le chargement de la page soit perturbé, et qu'il y ait des conflits. Il faut en effet au préalable s'assurer qu'aucun script ne se lance au même moment. S'il y en déjà un, il faut ajouter le nouveau script.
Heureusement, la méthode addEventListener
permet d'ajouter un gestionnaire à ceux qui existent éventuellement déjà. On peut alors l'utiliser, mais dans le cadre d'une méthode plus large, que nous appellerons addLoadListener
, qui est compatible avec les versions 8 et antérieures d'Internet Explorer
:
function
addLoadListener(gestionnaire){
if (window.addEventListener)
{
window.addEventListener('load'
gestionnaire
false) ;
}
else
if (document.addEventListener)
{
document.addEventListener('load'
gestionnaire
false) ;
}
else
if (window.attachEvent)
{
window.attachEvent('onload'
gestionnaire) ;
}
}
Un des inconvénients liés à load
est que cet événement n'est lancé qu'une fois tout le document chargé : DOM, images, feuilles de style, etc. L'événement DOMContentLoaded
est quant à lui lancé quand le DOM a été chargé, autrement dit la structure de la page, donc en particulier avant tout téléchargement d'image. On l'utilise en ajoutant le gestionnaire d'événement à l'objet document
: document.addEventListener("DOMContentLoaded", gestionnaire, false)
.
Afin de préserver au maximum les possibilités d'interagir avec le document avec le clavier, il est recommandé de n'affecter des événements de clic que sur des éléments susceptibles de recevoir le « focus » clavier, à savoir a
, area
et les éléments de formulaire.
Cette création est mise à disposition par Gilles Chagnon sous un contrat Creative Commons.