JavaScript – introduction

Logo officiel (?) de JavaScript (auteur inconnu)

« The strength of JavaScript is that you can do anything. The weakness is that you will » – Reginald Braithwaite (source)

« Java is to JavaScript what Car is to Carpet » – Chris Heilmann (source)

Quelques raccourcis :

Le langage ECMAScript, connu sous plusieurs noms, en particulier sous son nom d'origine qu'est JavaScript, est l'un des langages de scripts les plus populaires dans le monde merveilleux du Web. À peu près tous les fureteurs commercialement pertinents supportent au moins un dialecte de ce langage.

Bien que la dénomination JavaScript soit la plus connue et la plus commune, retenez que ECMAScript est le fruit de la standardisation de JavaScript (et de JScript et de Mocha et de LiveScript et...). Un langage de script est habituellement un langage interprété, donc traduit ligne par ligne, méthode par méthode ou instruction par instruction en code exécutable plutôt que compilé une fois pour être exécuté sur une plateforme donnée. Dans les cas qui nous intéressent, les interpréteurs sont les fureteurs Web ou des modules qui leur sont ajoutés.

Malgré son nom, JavaScript n'est pas un dialecte de Java ou un sous-ensemble de Java. Les similitudes syntaxiques de ce langage avec Java ressemblent à celles qui existent entre Java et C ou entre C# et Java. Les deux langages ne viennent même pas d'une souche commune, Java étant un produit de Sun Microsystems alors que JavaScript fut, à la base, mis au point par Brendan Eich de Netscape (aujourd'hui derrière le fureteur Brave).

Le suffixe « Script » tend à faire croire que JavaScript est un langage jouet, mais il se trouve en fait que ce langage entre dans le créneau de ce qu'on nomme les langages dynamiques, de même que dans la catégorie des langages fonctionnels. À plusieurs égards, d'ailleurs, JavaScript est plus près de Lisp que de Java.

Les langages dynamiques ont ceci de particulier que leur structure tend à être fluide et changeante, même une fois qu'un programme a été lancé. Selon les langages, il peut être possible de modifier le système de types, ajouter des classes, ajouter des fonctions et ainsi de suite. En général, pour être en mesure de permettre de telles manœuvres, les langages de scripts offrent aussi un mécanisme d'introspection permettant de décrire le langage dans ses propres termes (les programmeuses et les programmeurs Java et .NET reconnaîtront une similitude avec la réflexivité dans ces langages).

Le caractère interprété et dynamique de JavaScript le rend particulièrement propice à l'injection de comportements dynamiques dans une page Web, donc à l'accroissement de l'expérience de navigation sur Internet. Le fait que les langages dynamiques et, de manière générale, les langages de scripts soient pour la plupart interprétés implique aussi que les programmes écrits dans ces langages soient portables, une qualité indéniable pour le déploiement à travers Internet.

Déboguer du code JavaScript

JavaScript a mauvaise réputation en partie parce qu'il est réputé difficile à déboguer. Ce n'est pas tout à fait faux, historiquement du moins, mais les outils à notre disposition aujourd'hui font en sorte que déboguer un programme JavaScript soit une tâche beaucoup plus raisonnable et beaucoup moins pénible qu'auparavant.

Certains outils en ligne permettent de valider le code JavaScript; par exemple, http://www.jslint.com/ ou https://www.pcwdld.com/firebug-alternatives-javascript-debugging-tools. Autres outils pratiques : l'explorateur DOM et la console d'erreurs de certains fureteurs qui donnent des messages significatifs et font des liens avec les éléments pertinents de la page Web.

Comment fonctionne JavaScript dans une page Web

Dans une page Web, le code JavaScript peut être intégré directement dans le texte de la page et est en mesure d'interagir avec le modèle DOM du document qu'elle décrit. Son côté script est mis en valeur par le fait que le code dans la page, exécuté par le fureteur, peut réagir aux éléments de la page et les modifier.

Certains individus désactivent JavaScript dans leur fureteur, souvent à cause de problèmes de sécurité détectés avec leur technologie de prédilection. Règle générale, il est préférable d'avoir un « plan B » lorsqu'on a recours à JavaScript dans une page Web pour que celle-ci demeure utilisable même si cette particularité a été désactivée.

Les cas les plus simples de recours à JavaScript, traditionnellement, incluent la génération de fenêtres modales d'avertissement (des popups), les changements d'états de contrôles sur la page alors que la souris se déplace sur eux ou la validation des entrées de l'usager dans un formulaire.

Cela dit, JavaScript est un véritable langage de programmation, très polyvalent, ce qui rend la tâche de sécuriser les fureteurs foncièrement plus complexe qu'il n'y paraît a priori. Certaines attaques (comme les célèbres manœuvres de Cross-Site Scripting , ou XSS) qui permettent, surtout avec certains fureteurs plus fragiles, la réalisation d'opérations dangereuses comme des accès au disque local d'un ordinateur ou la capture d'informations privilégiées. Souvenons-nous en effet que JavaScript interagit avec le DOM d'un document, alors si une manoeuvre de programmation parvient à injecter du code JavaScript discrètement dans une page conçue imprudemment, il peut devenir possible d'envoyer des données confidentielles à un tiers hostile sans que l'humain devant le fureteur n'en soit conscient.

Voir http://en.wikipedia.org/wiki/Cross-site_scripting qui inclut entre autres la description de quelques cas réels d'assauts du genre (avec liens). Pour un cas vécu d'assaut par XSS, voir http://www.webmonkey.com/webmonkey/00/18/index3a.html

Le code JavaScript est simplement du code intégré à une page Web à l'intérieur d'une balise <script> correctement construite.

Ce faisant, la page Web devient un volet présentation dans lequel s'insère un programme sujet à être interprété (donc exécuté) par une machine virtuelle : le fureteur.

Étant intégré à même la page Web, le script a accès à la description DOM dans le fureteur du document qu'elle représente et peut agir sur celle-ci, en lecture et en écriture.

<html>
  <head>
    <title>Titre</title>
    <script type='text/javascript'>
      // le code va ici
    </script>
  </head>
  <body>
    <!-- le code de la page Web -->
  </body>
</html>

Intégrer du code JavaScript directement à une page Web pose problème à plusieurs égards :

La vision du Web comme plateforme de développement a plusieurs impacts sur les pratiques de développement qu'on y retrouve, incluant une standardisation des usages et une formalisation de certains vieux trucs sous forme de schémas de conception ou d'idiomes de programmation.

Plusieurs plateformes existent aujourd'hui pour formaliser une démarche plus programmatique que celle rencontrée traditionnellement dans les pages Web. Indiquer, par exemple, que tous les éléments du document qui sont munis d'une même classe ou d'un même nom soient soumis aux mêmes règles de validation (ce qui évite les accidents qui surviennent lorsqu'un humain doit insérer ces indications à la pièce).

La chose la plus simple que nous puissions faire, cela dit, pour séparer code JavaScript et présentation HTML est, tout simplement, de les séparer en fichiers distincts.

Ainsi, alors que la tradition suggère d'insérer le code JavaScript directement entre les balises <script> et </script> dans la page Web proposée à droite...

<html>
   <head>
      <title>Test</title>
      <script type='text/javascript'>
/* votre code JavaScript va ici */
      </script>
   </head>
<body>
  <!-- peu importe -->
</body>
</html>

... il est aussi possible de procéder comme proposé ici et de référer simplement à un fichier source JavaScript externe au code HTML.

On comprendra immédiatement l'intérêt de cette démarche : plusieurs pages Web peuvent partager un même support programmatique en référant aux mêmes fichiers de scripts, et la page Web elle-même en est allégée d'une masse de texte qui n'est pas essentielle à son rôle de présentation d'information.

Il est possible de référer à plusieurs fichiers JavaScript dans une même page Web; il suffit simplement d'insérer plusieurs balises <script>, qui seront lues dans l'ordre.

<html>
   <head>
      <title>Test</title>
      <script type='text/javascript'
              language="javascript"
              src="moncode.js" />
   </head>
<body>
  <!-- peu importe -->
</body>
</html>

Vous trouverez plus de détails à ce sujet sur http://en.wikipedia.org/wiki/Unobtrusive_JavaScript

Accéder à un élément de la représentation DOM d'un document

Dans un document HTML, les éléments peuvent être qualifiés par un nom, un identifiant, une classe et ainsi de suite

Par exemple, dans l'extrait proposé à droite, on trouve entre autres une balise input dont la classe est text (standard pour une zone de texte) et dont l'identifiant est resultat.

On trouve aussi un bouton de commande sur lequel apparaît le texte Additionner qui, lorsqu'il aura été sélectionné, invoquera la fonction JavaScript nommée calculerSomme().

Comment la fonction JavaScript pourra-t-elle savoir quelles sont les valeurs dans les zones de texte portant les identifiants val_a et val_b et comment sera-t-elle en mesure d'écrire dans la zone de texte portant l'identifiant resultat?

<!-- peu importe -->
<input class="text" id="val_a" />
<input class="text" id="val_b" />
<button onclick="calculerSomme();">
  Additionner
</button>
<input class="text" id="resultat" />
<!-- peu importe -->

La réponse est somme toute simple :

  • la représentation DOM du document est, au sens d'un programme JavaScript, un objet nommé document; et
  • la méthode getElementById() permet de retrouver un élément par son identifiant (présumé unique dans la page); ainsi
  • il suffit à la fonction de saisir les valeurs de ces deux éléments, de les manipuler et de déposer le résultat dans la valeur de l'élément portant l'identifiant resultat.
function calculerSomme () {  var x = new Number(
    document.getElementById("val_a").value
  );
  var y = new Number(
    document.getElementById("val_b").value
  );
  var somme = x + y;
  document.getElementById("resultat").value = somme;
}

JavaScript et les événements

Au préalable, le principal rôle de JavaScript était de valider le contenu des formulaires Web avant émission. Cette validation ne remplace pas la validation côté serveur (nécessaire parce que le monde demeure un milieu hostile) mais peut par exemple permettre d'éviter l'envoi d'un formulaire dont certains champs obligatoires n'ont pas été remplis.

Présumant l'ébauche de code à droite, l'envoi des données contenues entre les balises <form> et </form> impliquera au préalable l'invocation de la fonction JavaScript valider() qu'on présumera booléenne et dont le rôle sera de valider les données avant envoi.

<form action="operationCoteServeur" onsubmit="return valider()">
  <!-- peu importe -->
</form>

Si la fonction retourne false ou quelque chose qui, en JavaScript, équivaut au concept de faux, alors il n'y aura pas du tout d'envoi de données vers le serveur, économisant ainsi temps de traitement et bande passante.

La fonction onsubmit est la réaction à un événement de soumission de données du formulaire, tout comme la fonction onclick d'un bouton de commande est la réaction à un clic d'un bouton de commande. Pour la validation de données textuelles, il est fréquent que la méthode onchange soit remplacée par du code maison.

Règle générale, JavaScript permet de remplacer le code par défaut des divers événements susceptibles de se produire sur une page Web suite à une interaction avec un usager par du code développé et choisi par les conceptrices et les concepteurs de la page.

Un petit mais intéressant exemple de recours à JavaScript pour dynamiser le contenu d'une page Web serait celui proposé à droite.

On y voit un exemple de style CSS nommé exemple qui a la particularité d'avoir un attribut nommé display dont la valeur initiale est hidden. La principale conséquence de ce choix est que, dans la page représentée, tout élément dont la classe est exemple ne sera pas affiché.

La fonction JavaScript nommée modifierAffichage() fera passer l'état de l'attribut display de none à block (dans une boîte) et inversement.

Nous obtenons ainsi une page Web dont certains éléments seront affichés ou non selon les actions (et les besoins) d'un usager.

<html>
  <head>
    <title>Petite d&eacute;mo</title>
    <style type="text/css">
      #exemple {
        display: none; margin: 3%; padding: 4%;
        background-color: limegreen
      }
    </style>
    <script type="text/javascript">
      function modifierAffichage (id) {
        d=document.getElementById("cacher_ou_non");
        e=document.getElementById(id);
        if (e.style.display == 'none' ||
            e.style.display == "") {
          e.style.display = 'block';
        } else {
          e.style.display = 'none';
        }
      }
    </script>
  </head>
  <body>
    <div>
      <a id="cacher_ou_non"
         href="javascript:modifierAffichage('exemple')">
           Petit exemple chouette
      </a>
    </div>
    <div id="exemple">
      Un exemple pourrait aller ici
    </div>
    <p>Et ainsi de suite...</p>
  </body>
</html>

Bases syntaxiques de JavaScript

Ce qui suit s'applique à la version 1 de JavaScript (JS1), et devra être retouché en vue des standardisations plus récentes. Cela dit, il serait étonnant que le code de cette section devienne invalide; il est simplement probable que certaines pratiques se soient raffinées avec le temps.

Pour plus d'informations :

Le langage JavaScript a plusieurs particularités. Tout d'abord, à l'instar de plusieurs langages dynamiques, ce n'est pas un langage fortement typé ce qui implique que ses variables sont nommées puis utilisées et que c'est le moteur du langage qui cherche à comprendre pour nous ce que nous cherchons à faire.

Ceci implique aussi qu'il n'y a pas, en JavaScript, de déclaration de variable à proprement dit. Dans ce langage, une variable existe dès qu'on la nomme et dès qu'on commence à l'utiliser. De plus, toutes les variables y sont globales sauf si elles sont préfixées de var lors de leur première utilisation (dans ce cas, elles sont locales au bloc où elles apparaissent). Cet énoncé demeure vrai que la variable soit initialisée dans une fonction ou hors de toute fonction.

Ainsi, le code à droite est légal du fait que val y est globale et est donc accessible aux deux fonctions.

Un bémol, toutefois : si ce code invoque bonjour() avant la première invocation de coucou(), alors une erreur surviendra car val ne sera pas initialisée et l'appel à alert() (qui affiche une fenêtre modale d'avertissement) échouera. (en fait, il affichera probablement Bonjour!null).

function coucou() {
   val = 3;
   alert('Coucou!' + val);
}
function bonjour() {
   alert('Bonjour!' + val);
}

Vrai ou faux

Le langage JavaScript étant dynamique, une convention est requise pour les opérations reposant sur l'évaluation d'un booléen.

Ainsi, sont considérés faux le littéral false, une chaîne vide, un NaN et les constantes null et undefined (cette dernière étant la valeur des variables déclarées mais non initialisées). Toutes les autres valeurs sont considérées vraies au sens de l'algèbre booléenne.

À ce sujet :

Personnellement, je recommande des saines pratiques de programmation : réduisez les globales au minimum et utilisez var chaque fois que cela s'avère possible.

Comme dans bien des langages à accolades, chaque instruction et chaque déclaration est terminée par un ; et chaque bloc logique d'instructions est délimité par une paire d'accolades. Les commentaires suivent la même forme que ceux en C++ ou en Java. Le langage est sensible aux majuscules et aux minuscules.

Le code JavaScript est analysé de haut en bas par son interpréteur. Le code qui ne se situe pas dans une fonction est du code global qui sera exécuté dans l'ordre.

Un coût pas si caché des langages de scripts

Le fait que le texte (incluant les blancs entre les mots!) du code JavaScript accompagne la page Web à laquelle il s'applique peut ralentir les téléchargements de cette page.

Plusieurs techniques sont possibles pour compresser le texte HTML transmis entre le serveur Web et le fureteur, incluant un transfert compressé (voir la documentation de votre serveur) ou le recours à des outils de transformation des sources comme par exemple JSMin.

Opérateurs

L'opérateur + sert à la fois aux additions numériques (var x = 2 + 3; dépose 5 dans x) et à la concaténation de texte (var x = 2 + '3'; dépose '23' dans x).

Les opérateurs arithmétiques usuels (+, évidemment, de même que -, *, / et %) se comportent comme en Java ou en C++. L'affectation s'exprime par l'opérateur =. Les opérateurs ++ et -- (en version préfixée ou postfixée) et les opérateurs +=, -=, *=, /= et %= se comportent aussi comme en Java ou en C++.

Les opérateurs de comparaison habituels dans les langages à accolades (==, !=, <, <=, >, >=) fonctionnent selon les usages. Deux opérateurs s'ajoutent, soit === pour identiques (même valeur, même type) et !== (non identiques).

Les opérateurs logiques &&, || et ! ont le même comportement qu'en C++ pour la majorité des cas (je ne couvrirai pas les subtilités ici mais il y a une subtilité autour de && et ||). Les opérateurs bit à bit <<, >>, & et | ont aussi le comportement attendu (et l'opérateur >>> sort complètement de notre champ d'intérêt). L'opérateur ternaire ?: fonctionne tel qu'attendu.

Scalaires

Les nombres de JavaScript sont tous structurellement équivalent aux nombres à virgule flottante (double précision… des variables de type double) de C++ ou de Java. Ceci implique des risques d'imprécision lors d'affichages et lors de comparaisons (prudence!). Les notations habituelles en C++ ou en Java pour les littéraux numériques sont supportées (3, 3.14159, 2.12e4, 0xff, 0177) mais les variables contenant des nombres demeurent de format double peu importe la forme des littéraux qu'on leur affecte.

Le caractère non typé des variables entraîne des surprises. Conséquemment, soyez prudentes et prudents. Le code proposé à droite affichera Coucou!34, pas Coucou!7, du fait que val contient le texte "3" plutôt que la valeur numérique 3.

function coucou() {
   var val = "3";
   alert('Coucou!' + (val + 4));
}

Pour résoudre de tels imbroglios, on peut remplacer val par new Number(val) dans l'opération (val + 4). Ainsi, alert('Coucou!' + (new Number (val) + 4)) affichera Coucou!7. Le recours à l'instanciation d'un Number clarifie notre intention.

Pour la concaténation de chaînes, les formes x = x + "texte" et x += "texte" mènent toutes deux au même résultat.

Vous remarquerez que les notations 'allo' et "allo" sont toutes deux supportées pour dénoter des chaînes de caractères. Dans une page Web, il arrive que l'une soit plus utile que l'autre selon les circonstances.

En JavaScript comme en Java, une chaîne de caractères est immuable et ne peut donc pas être modifiée une fois instanciée. Il est possible de lire les caractères un par un (en utilisant la notation indicée des tableaux) mais il est impossible de les remplacer.

L'exemple à droite affichera deux fois le texte Coucou commence par un C.

function coucou() {
   var texte = "Coucou!";
   var premièreLettre = texte[0];
   alert(texte + " commence par un " + premièreLettre);
   texte[0] = "W"; // illégal
   alert(texte + " commence par un " + premièreLettre);
}

Structures de contrôle

Les alternatives (if), les répétitives (while, do ... while et for) et les sélectives (switch) ont le même comportement qu'en C++ ou en Java, à ceci près qu'une sélective peut travailler avec des chaînes de caractères comme avec des nombres (Java permet ceci, C++ ne le permet pas).

Une forme un peu particulière (for(x in E) { ... }) permet d'itérer à travers les éléments (en fait, à travers les clés) d'un tableau.

Les exceptions ont la même forme qu'en Java (try, catch, finally).

On peut lever toutes sortes d'objets en JavaScript. Il est d'ailleurs fréquent que de simples chaînes de caractères soient levées en tant qu'exceptions.

L'exemple de invoquerDiviser() proposé à droite sert de paravent à la fonction diviser() qui est susceptible de lever une exception si son dénominateur est nul. Bien que le code puisse être simplifié (et de beaucoup), il montre comment :

  • lever une exception (dans la fonction diviser())
  • mettre en place des blocs try, catch et finally (à l'intérieur de la fonction invoquerDiviser()); et
  • utiliser l'exception attrapée par un bloc catch

Certains fureteurs permettent de spécialiser les cas d'exceptions mais, à ma connaissance, cette tolérance ne fait pas partie du standard du langage.

<html>
   <head>
      <title>Division</title>
      <script type='text/javascript'>
function diviser(num, denom) {
  if (denom == 0) {
    throw "tentative de division par zéro!";
  }
  return num / denom;
}
function invoquerDiviser() {
  var numérateur = new Number(
    document.getElementById("numerateur").value
  );
  var dénominateur = new Number(
    document.getElementById("denominateur").value
  );
  var quotient;
  var résultat;
  try {
    quotient = diviser(numérateur, dénominateur);
    résultat = quotient;
  } catch (e) {
    résultat = e;
  } finally {
    document.getElementById("quotient").value =
       résultat;
  }
}
      </script>
   </head>
<body>
  <input class="text" id="numerateur"></input>
  <input class="text" id="denominateur"></input>
  <button onclick="invoquerDiviser();">
    Diviser
  </button>
  <input class="text" id="quotient"></input>
</body>
</html>

Tableaux

Les tableaux de JavaScript sont des tableaux associatifs, un peu comme le sont les std::map de C++ . Cela implique qu'on puisse attribuer des valeurs à des positions logiques représentant des clés.

L'exemple à droite utilise des clés numériques, un peu comme dans un tableau classique.

function coucou () {
   var ménagerie = new Array(3);
   ménagerie[0] = "chats";
   ménagerie[1] = "lapins";
   ménagerie[2] = "poissons";
   if (ménagerie.length > 0) {
      var texte = "Dans ma maison, il y a: ";
      for (i in ménagerie) {
         texte = " " + texte + "des " + ménagerie[i];
      }
      alert(texte);
   }
}

Remarquez au passage la boucle for qui itère sur les clés (pas sur les valeurs) du tableau. Une notation plus compacte pour initialiser des tableaux serait celle proposée à droite.

function coucou() {
   var ménagerie = ["chats", "lapins", "poissons"];
   var texte = "Dans ma maison, il y a: ";
   for (i in ménagerie) {
      texte = " " + texte + "des " + ménagerie[i];
   }
   alert(texte);
}

L'exemple à droite utilise quant à lui des clés textuelles et fonctionne précisément de la même manière (et avec le même effet) que le code de l'exemple précédent.

On remarquera qu'en JavaScript, itérer sur des clés texte ou sur des clés numériques revient à réaliser la même opération.

Les notations possibles pour opérer sur des tableaux sont nombreuses; référez-vous à la documentation en ligne pour des exemples plus riches que ce que permet ce bref survol.

function coucou () {
   var ménagerie = new Array(3);
   ménagerie["félins"] = "chats";
   ménagerie["lagomorphes"] = "lapins";
   ménagerie["marins"] = "poissons";
   if (ménagerie.length > 0) {
      var texte = "Dans ma maison, il y a: ";
      for (i in ménagerie) {
         texte = " " + texte + "des " + ménagerie[i];
      }
      alert(texte);
   }
}

Fonctions

JavaScript est un langage dynamique et fonctionnel, au sens où les fonctions y sont des entités à part entière.

On peut déposer une fonction dans une variable (ce qui n'est pas la même chose que déposer le résultat de l'invocation d'une fonction dans une variable), par exemple, ou écrire une fonction qui retourne des fonctions.

L'exemple à droite montre une page Web (vraiment très simple) dans laquelle est définie la fonction factorielle() utilisant des éléments du document tel que perçu par le fureteur en tant qu'intrants et en tant qu'extrants.

Par souci d'esthétique (et non pas par obligation), j'ai choisi d'écrire une fonction factorielle() réutilisable et une autre fonction, liée plus spécifiquement au document, pour l'invoquer.

<html>
  <head>
    <title>Factorielle</title>
    <script type='text/javascript'>
      function factorielle(n) {
        var facto = 1;
        while (n > 0) {
          facto *= n;
          --n;
        }
        return facto;
      }
      function invoquerFactorielle() {
        var n = new Number(
          document.getElementById("valeur_n").value
        );
        var facto = factorielle(n);
        document.getElementById("resultat").value = facto;
      }
    </script>
  </head>
  <body>
    <input class="text" id="valeur_n"></input>
    <button onclick="invoquerFactorielle();">
      Factorielle
    </button>
    <input class="text" id="resultat"></input>
  </body>
</html>

On remarquera que le caractère dynamique du langage est mis en relief par la notation de la fonction : elle n'a pas de type (sinon celui de function), ses variables non plus (bien qu'on remarque le recours à une conversion explicite en Number pour éviter toute ambigüité).

Avec des multiplications comme dans le cas d'une factorielle, c'est probablement une opération superflue. Cela dit, sachant que l'opérateur + peut vouloir dire concaténer ou additionner selon le type de ses opérandes (type tel que perçu par le moteur exécutant le script, ne l'oublions pas), nous pourrions avoir de vilaines surprises si nous cherchions plutôt à faire une sommation.

Il est possible, tel qu'indiqué plus haut, d'affecter une fonction à une variable. Ainsi, l'exemple de code JavaScript à droite se lit comme suit :

  • trois fonctions, nommées choixParDéfaut(), choisirA() et choisirB(), retournent chacune une fonction susceptible d'être exécutée éventuellement
  • la variable globale choix reçoit initialement la valeur de choixParDéfaut(), donc une fonction
  • invoquer choixA() ou choixB() dépose dans choix une fonction anonyme, elle aussi susceptible d'être exécutée
  • la fonction executer(), enfin, invoque la fonction dans la variable choix en en affiche le résultat dans une petite fenêtre

Une conséquence de cette stratégie : les boutons Choisir A et Choisir B déterminent le plus récent choix fait mais le bouton Exécuter, lui, invoque véritablement la fonction choisie.

<html>
   <head>
      <title>Test &ndash; 00</title>
      <script type='text/javascript'>
        function choixParDéfaut() {
          return function() {
            return "Vous n'avez pas encore choisi!";
          }
        }
        function choisirA() {
          choix = function() {
            return "Vous avez choisi A";
          }
        }
        function choisirB() {
          choix = function() {
            return "Vous avez choisi B";
          }
        }
        choix = choixParDéfaut();
        function executer() {
          alert(choix());
        }
      </script>
   </head>
<body>
  <button onclick="choisirA();">
    Choisir A
  </button>
  <button onclick="choisirB();">
    Choisir B
  </button>
  <button onclick="executer();">
    Ex&eacute;cuter!
  </button>
</body>
</html>

Notez que comme en Java, les paramètres de fonctions en JavaScript sont passés par valeur mais les objets étant toujours manipulés indirectement, le passage en paramètre d'un objet revient dans la majorité des cas à un passage par référence.

Objets

Les objets JavaScript possèdent des attributs nommés. Dans l'exemple à droite, l'objet ChicProf contient un nom et un cours, ce dernier contenant quant à lui un nom et un sigle.

On remarquera que le nom d'un attribut est séparé de sa valeur par un : et que les attributs sont séparés les uns des autres par des virgules.

Pour en savoir plus sur cette notation qu'on nomme JSON, voir JSON.html

function Coucou() {
   var ChicProf = {
      nom : "Patrice Roy",
      cours : {
         nom : "Applications Internet",
         sigle : "INF777"
      },
   };
   alert(ChicProf.nom + " est un chic prof de " + ChicProf.cours.sigle);
}

La notation des tableaux est aussi applicable aux objets : dans l'exemple ci-dessus, ChicProf.nom et ChicProf['nom'] sont équivalents. La contrepartie n'est pas vraie : on ne peut pas appliquer la notation .nom à un tableau indicé par le littéral texte 'nom'.

L'exemple à droite montre :

  • un constructeur de Bête (notez le nom de la méthode et le recours explicite à this) prenant en paramètre un nom, une espèce et un poids
  • une fonction de fabrication de dragons nommée créerDragon() (qui utilise le constructeur de Bête… Notez le recours à new); et
  • une fonction surprise() qui crée un dragon rouge nommé Zak et le décrit dans une petite fenêtre modale
function Bête(nom, espèce, poids) {
  this.nom = nom;
  this.espèce = espèce;
  this.poids = poids;
}
function créerDragon(nom, couleur, poids) {
  return new Bête(
    nom, "Dragon " + couleur, poids
  );
}
function surprise() {
  var Zak = créerDragon("Zak", "rouge", 3000);
  alert(Zak.nom + " est un " + Zak.espèce + " de " + Zak.poids + " Kg.");
}

Le caractère dynamique de JavaScript mène à une fluidité structurelle qui pourra surprendre certaines et certains parmi vous. En effet, il est possible d'ajouter ou d'enlever dynamiquement des attributs à un objet existant.

Présumant que le constructeur de Bête et la fonction créerDragon() du code ci-dessus soient appelés au préalable, invoquer surprise() dans le code à droite a l'effet suivant :

  • créer le dragon Zak et en afficher le pédigrée
  • chercher à le faire voler (un échec puisqu'il n'a pas encore d'ailes);
  • lui ajouter des ailes
  • chercher à le faire voler (un succès puisqu'il a maintenant de grandes ailes)
  • lui couper les ailes (remarquez l'opérateur delete sur un attribut); et
  • chercher à le faire voler (un échec puisqu'il n'a plus d'ailes)

Bien que JavaScript soit pourvu d'un ramasse-miettes, il est possible d'y détruire explicitement des objets ou des membres d'objets à l'aide de l'opérateur delete et de tester la présence d'un membre en le comparant avec la constante null.

// constructeur de Bête (inchangé)
// créerDragon() (inchangé)
function surprise() {
  var Zak = créerDragon(
    "Zak", "rouge", 3000
  );
  alert(Zak.nom + " est un " + Zak.espèce + " de " + Zak.poids + " Kg.");
  envol(Zak);
  ajouterAiles(Zak, 'grandes');
  envol(Zak);
  couperAiles(Zak);
  envol(Zak);
}
function ajouterAiles(bête, valeur) {
  bête.ailes = valeur;
}
function couperAiles(bête) {
  if (bête.ailes != null) {
    delete bête.ailes;
  }
}
function envol(bête) {
  if (bête.ailes != null) {
    alert(bête.nom + " peut voler");
  } else {
    alert(bête.nom + " n'a pas d'ailes!");
  }
}

Encapsulation

Cette section n'est qu'un très bref survol. Voir http://www.crockford.com/javascript/private.html pour plus de détails

Les attributs proposés plus haut étaient tous publics. Ceci ne suffit pas, en pratique, à assurer une forme stricte d'encapsulation dans un objet.

Pour déclarer un membre d'instance privé, il suffit de le spécifier avec var dans son constructeur. Ceci s'applique aux attributs comme aux méthodes.

L'exemple (simplifié) à droite montre comment placer un accesseur public simple, getNom(), pour le nom d'une Bête à même sa construction.

function Bête(unNom) {
  var nom = unNom;
  this.getNom = function() {
    return nom;
  }
  // ...
}
function surprise() {
  var Zak = new Bête("Zak");
  alert(Zak.getNom()); // etc.
}

Notez qu'en JavaScript, une méthode d'instance privée est une méthode déclarée dans le constructeur de l'objet auquel elle appartient (et donc invisible de l'extérieur).

Selon l'usage en JavaScript, la méthode getNom() d'une Bête est ce qu'on nomme une méthode privilégiée, en ce sens qu'elle a accès à la fois aux membres privés d'une Bête et qu'elle est accessible de l'extérieur, ayant été ajoutée à la Bête en question suivant la forme this.getNom = function() { ... }.

Ce que JavaScript considère comme une méthode publique est une méthode ajoutée au prototype d'une classe.

Ainsi, la méthode grogner() d'une Bête dans l'exemple à droite est publique. On remarquera que les méthodes publiques au sens entendu en JavaScript sont ajoutées suite à la construction de l'objet et sont donc des compléments externes à cet objet. En particulier, elles n'ont pas accès aux membres privés.

Remarquez le recours à this devant getNom() dans grogner(), qui est nécessaire en JavaScript d'un point de vue syntaxique.

function Bête(unNom) {
  var nom = unNom;
  this.getNom = function() {
    return nom;
  }
  // ...
}
Bête.prototype.grogner = function(texte) {
  return this.getNom() + " fait " + texte;
}
function surprise() {
  var Zak = new Bête("Zak");
  alert(Zak.grogner("grrr")); // etc.
}

Encapsulation et immuabilité sans attributs

Une forme compacte (prise directement d'un exemple proposé par le concepteur du langage) de construction d'une instance immuable d'une classe Point serait celle visible à droite. Notez qu'il s'agit d'un objet sans attributs à proprement dit, représenté par une fonction contenant des fonctions et qui est pleinement encapsulé.

Cette stratégie a le défaut de coûter cher (en JavaScript, une représentation sous forme de fonctions comme celle-ci coûte environ le triple de l'espace utilisé pour la représentation équivalente sous forme d'attributs pour chaque instance de Point), mais cela ne devient vraiment important que si le nombre d'objets devient grand.

function Point(x, y) {
  return {
    getX() : { return x; }
    getY() : { return y; }
  }
}

Héritage

JavaScript ne supporte pas l'héritage au sens classique du terme et ne supporte pas non plus le polymorphisme en ce sens. Cependant, son caractère dynamique fait en sorte qu'un peu comme dans les langages permettant la programmation générique, il soit possible d'implémenter en JavaScript des algorithmes génériques complexes sur la base des signatures des objets.

Pour en savoir plus :

Quelques liens sur JavaScript

Pour en savoir plus sur JavaScript, voici quelques pistes de lecture. Pour des liens d'ordre général :

Pédagogie

Futur du langage

Histoire du langage

JavaScript et l'approche orientée objet

Tel que mentionné plus haut, JavaScript n'est pas un langage orienté objet (OO) mais entretient tout de même des liens avec ce monde. À ce sujet, quelques pistes :

Trucs et techniques

Plusieurs critiques de JavaScript tiennent au traitement que fait ce langage des idées de vrai et de faux. Pour en savoir plus, voir ceci.

Critiques

Exercices

EX00 – Insérez dans une page Web une procédure de validation assurant qu’un champ dans un formulaire soit bel et bien rempli avant envoi vers le serveur.

EX01 – Insérez dans une page Web une procédure de validation assurant que tous les champs d’un formulaire soient remplis avant envoi vers le serveur (truc : construisez un tableau des id des contrôles à valider avant d’invoquer la méthode de validation).

EX02 – Insérez dans une page Web une procédure remplaçant le texte de chaque zone de texte par son équivalent en minuscules avant validation.

EX03 – Insérez dans une page Web une procédure faisant en sorte que les données numériques d’un champ soient inclusivement situées entre 0 et 100.

EX04 – Insérez dans une page Web une procédure faisant en sorte que les champs devant obligatoirement être remplis soient identifiés par une couleur différente s’ils n’ont pas été remplis avant une tentative de soumission.

EX05 – Réalisez un exercice dans la liste allant de EX00 à EX04 inclusivement sur une page générée (au moins en partie) par un Servlet.


Valid XHTML 1.0 Transitional

CSS Valide !