4 : Objets Javascript

Les objets

Le JavaScript, un langage orienté objet

Le JavaScript est un langage orienté objet.

Cela signifie que ce langage a été construit autour du concept d’objets, et que son fonctionnement même se base sur l’utilisation d’objets.

En effet, en JavaScript, quasiment tout est avant tout objet :

  • Les chaînes de caractères, nombres et booléens peuvent être des objets (ou des valeurs primitives traitées comme des objets) ;
  • Les fonctions sont des objets ;
  • Les tableaux sont des objets ;
  • Les dates sont des objets ;
  • Les expressions régulières sont des objets.

En bref, vous devez absolument comprendre ce que sont les objets et comment ils fonctionnent afin de bien comprendre le JavaScript.

Qu’est-ce qu’un objet ?

Dans la « vraie » vie, par exemple, un crayon est un objet.

Il existe des crayons de différentes couleurs et de différentes tailles ou formes. La couleur, taille ou forme d’un crayon vont être des propriétés.

Tous les crayons vont posséder les mêmes propriétés (tous les crayons possèdent une taille) mais les valeurs associées à ces propriétés vont être différentes (chaque crayon peut avoir une taille différente des autres).

De plus, un crayon sert à écrire. “Écrire” est donc ici une fonction de l’objet crayon ou encore ce que nous allons désormais appeler une méthode.

Les objets en JavaScript

En JavaScript, tout comme dans la vraie vie, un objet va contenir un ensemble de propriétés et de méthodes.

Dans ce cours, nous avons déjà vu ce qu’étaient les variables et avons appris à les manipuler.

Nous avons découvert que les variables pouvaient stocker différents types de valeurs comme une chaîne de caractères, un nombre, un booléen, etc.

Cependant, jusqu’à présent, nous n’avons fait stocker qu’une seule valeur à la fois à nos variables. Plus précisément, nous avons fait stocker ce que l’on appelle des valeurs primitives à nos variables.

En réalité, les variables en JavaScript vont toujours stocker soit des valeurs primitives, soit des objets.

Comme un objet peut contenir différentes propriétés (et méthodes) et leurs valeurs associées, nos variables vont également de fait pouvoir stocker plusieurs valeurs lorsqu’elles vont stocker un objet.

Propriétés et méthodes des objets

Les objets en JavaScript vont contenir des propriétés et méthodes.

Les « propriétés » d’un objet peuvent être assimilées à des variables telles que nous les avons vues précédemment. Elles sont constituées d’un nom et d’une valeur séparés par deux points (« : »).

Les « méthodes » d’un objet peuvent être assimilées à des fonctions qui vont elles mêmes être stockées dans des propriétés.

/* cette variable stocke une valeur primitive */
var primitive = "Je suis une valeur primitive";
 
/* cette variable stocke un objet */
var moi = {
     /* les paires "nom:valeurs" sont des propriétés */
     prenom : "Jean",
     nom : "Dupont",
     age : 25,
     /* identite est une propriété */
     /* identite() est une méthode */
     identite : function() {
          return 'Prénom : ' + this.prenom +
                 '\nNom : ' + this.nom +
                 '\nAge : ' + this.age ;
     }
};

Création d’objets en JavaScript

Nous disposons de trois façons pour créer de nouveaux objets :

  • On peut utiliser le mot clé new et créer un objet à partir de Object() ;
  • On peut créer un objet littéral (méthode généralement recommandée) ;
  • On peut définir un constructeur puis créer des objets à partir de celui-ci.
/* on crée un objet en utilisant new Object() */
var moi = new Object();
/* on définit des propriétés pour notre objet */
moi.nom = "Dupont";
moi.prenom = "Jean";
moi.age = "25";
/* on affiche la valeur liée à prénom */
alert(moi.prenom);
 
/* on crée un objet littéral */
var toi= {
     /* les paires "nom:valeurs" sont des propriétés */
     prenom : "Jean",
     nom : "Dupont",
     age : 25,
     /* identite est une propriété */
     /* identite() est une méthode */
     identite : function() {
          return 'Prénom : ' + this.prenom +
                 '\nNom : ' + this.nom +
                 '\nAge : ' + this.age ;
     }
};
/* on affiche le résultat retourné par notre méthode */
alert(toi.identite());
 
/* on crée un objet à partir d'un constructeur */
function Identite(p, n, a) {
     this.nom = n;
     this.prenom = p;
     this.age = a;
}
var jean = new Identite("Jean", "Dupont", 25);
var pierre = new Identite("Pierre", "Durand", 21);
/* on peur utiliser les données */
alert('Age de Jean : ' + jean.age);

L’identité des objets

Faites bien attention à un point avec les objets : au contraire des valeurs primitives, on y accède par référence et non pas par valeur.

Ainsi, on ne peut pas ni « comparer » ni « copier » un objet car cela n’a tout simplement pas de sens.

Prenons un exemple concret afin de bien comprendre. Pour cela, nous allons comparer le comportement des valeurs primitives et des objets.

Lorsque l’on travaille avec des variables qui contiennent des valeurs primitives, on peut tout à fait créer des « copies » de ces variables puis ensuite modifier les copies sans que cela impacte la valeur contenue dans la variable originale.

Avec les objets, cela va être complètement différent puisqu’on ne va pas pouvoir créer directement de « copie » d’un objet. En fait, on ne va créer qu’une autre référence pointant vers le même objet.

Ainsi, si l’on modifie par la suite l’une des valeurs d’une des références, on modifie également celle des autres. Regardez plutôt l’exemple suivant pour vous en convaincre :

function Identite(p, n, a) {
     this.nom = n;
     this.prenom = p;
     this.age = a;
}
var jean = new Identite("Jean", "Dupont", 25);
var victor = jean;
 
/* si on modifie la valeur du prénom de victor, jean sera également impacté car victor et jean représentent le même objet */
victor.prenom = "victor";
alert('Prénom de jean : ' + jean.prenom +
      '\nPrénom de victor : ' + victor.prenom );

Faîtes bien attention à ce point car beaucoup de développeurs, même expérimentés, font l’erreur.

La portée en JavaScript

La portée désigne la possibilité d’accéder ou non à certaines variables (et donc aux valeurs, fonctions ou objets contenus à l’intérieur de ces variables) depuis un endroit précis d’un script.

En JavaScript, les variables peuvent être définies soit localement, soit globalement dans le script.

La portée va influer sur le fait qu’on puisse récupérer la valeur qu’elles contiennent et travailler avec ces variables n’importe où dans notre script ou non.

Les variables locales

Une variable est dite « locale » lorsqu’elle est définie à l’intérieur d’une fonction (donc localement).

Une variable locale n’est accessible que localement, c’est-à-dire seulement à un endroit spécifique dans le script.

Par exemple, lorsqu’on déclare une variable à l’intérieur d’une fonction, on ne pourra utiliser cette variable qu’à l’intérieur même de notre fonction.

Par défaut, cette variable ne sera pas accessible depuis le reste de notre script. Voyons immédiatement quelques exemples pour nous en assurer.

function locale() {
     var x = 5;
     alert(x);
}
locale();
alert(x);

Variables globales

Une variable est globale si elle est définie dans l’espace global d’un script, c’est-à-dire en dehors de toute fonction.

Une variable globale possède une portée globale et est donc accessible depuis n’importe où dans un script, même à l’intérieur des fonctions (attention, nous venons de voir que la réciproque est fausse).

var x = 6;
function locale() {
     alert(x);
}
locale();
alert(x);

Variable locale ou globale ?

Ici, on va se servir de ce qu’on a appris sur la portée en JavaScript pour observer des choses intéressantes.

Nous allons créer deux variables portant le même nom, une dans l’espace global et une à l’intérieur d’une fonction.

Ces deux variables vont posséder le même nom mais être bien différentes.

Le but ici va être d’afficher simultanément le contenu de ces deux variables afin d’observer ce qui est renvoyé par le JavaScript.

var x = 4;
function locale() {
     var x = 7;
     return x;
}
locale();
alert('x globale = ' + x + 
      '\nx locale = ' + locale() );

Le mot clef var

Vous devez savoir que si vous omettez le mot clef var lorsque vous voulez créer une variable, le JavaScript va automatiquement créer une sorte de variable globale.

function locale() {
     x = 7;
     return x;
}
locale();
alert(x);

Les closures en Javascript

Une closure (de « close », « fermé » en anglais) est une fonction qui va récupérer et pouvoir utiliser des variables d’environnement dans laquelle elle a été créée.

Autrement dit, une closure va nous permettre d’isoler ou d’enfermer une partie de code et de pouvoir utiliser et récupérer des variables qui ne sont accessibles normalement que depuis l’intérieur de ce code.

Lorsque l’on explique le principe de closures, il est coutume de prendre l’exemple de la création d’un compteur. Commencez donc par observer le code ci-dessous.

function compteur() {
     var i = 0;
     return function() {
          return i++;
     }
}
alert(compteur());

Comme vous le voyez, on crée une fonction compteur(). Cette fonction initialise une variable i et retourne une fonction anonyme.

Soyez bien attentif par rapport à cette fonction anonyme : on remarque que celle-ci se ressert de la variable i définie dans sa fonction parente et retourne la valeur de i incrémentée.

Commencez déjà par noter qu’en JavaScript il est tout à fait normal et naturel qu’une fonction ait accès aux variables de sa fonction parente (on parle de parent car on voit bien que notre fonction anonyme est enfermée dans notre fonction compteur).

Cependant, notez ici un point très intéressant : si jamais on appelle compteur(), la fonction anonyme va être renvoyée mais pas exécutée immédiatement.

En effet, pour exécuter une fonction anonyme il faut soit l’enfermer dans une variable soit la transformer en fonction auto-invoquée en lui ajoutant deux couples de parenthèses.

Résumons donc la situation : si on appelle compteur(), une variable i est initialisée et notre fonction anonyme est renvoyée mais pas exécutée.

Ici, la logique voudrait que si on exécute ENSUITE notre fonction anonyme, la variable i ne soit plus accessible et donc que la fonction anonyme ne renvoie rien du tout puisque notre fonction compteur() dans laquelle la variable i a été déclarée a déjà fini son exécution.

Eh bien non, et c’est là toute la magie des closures. Même après la fin de l’exécution de notre fonction compteur(), notre fonction anonyme va toujours pouvoir se servir de cette variable i.

Il est maintenant temps de terminer notre closure à proprement parler en utilisant une nouvelle variable pour « fermer » le code ci-dessus et exécuter notre fonction anonyme.

Notez qu’on aurait tout à fait pu utiliser une fonction auto-inovquée à la place de ma fonction compteur() ci-dessous.

function compteur() {
     var i = 0;
     return function() {
          return i++;
     }
}
/* on stocke compteur() dans une variable plusUn */
var plusUn = compteur();
/* on affiche plusUn en n'oubliant pas le couple de parenthèse pour exécuter la fonction anonyme */
alert('Premier tour : ' + plusUn() +
      '\nDeuxième tour : ' + plusUn() +
      '\nTroisième tour : ' + plusUn() );

Ce code comporte deux immenses avantages : d’une part, notre variable i est « protégée » au sens où elle ne peut pas être modifiée par l’environnement extérieur. D’autre part, on va pouvoir créer autant d’instances de notre fonction compteur() que l’on souhaite.

Typiquement, notre variable (qui contient une fonction et qu’on peut donc également appeler fonction tout simplement) plusUn est ce qu’on appelle une closure.

plusUn peut utiliser i à loisir, en se souvenant à chaque fois de sa valeur ce qui fait que notre compteur va s’incrémenter correctement.

De plus, on va pouvoir créer de nouvelles instances en rappelant notre fonction compteur(), et donc travailler avec plusieurs variables i aux valeurs différentes.

function compteur() {
     var i = 0;
     return function() {
          return i++;
     }
}
/* on stocke compteur() dans une variable plusUn */
var plusUn = compteur();
/* on peut créer autant d'instance que l'on souhaite */
var autreInstance = compteur();
/* on affiche plusUn en n'oubliant pas le couple de parenthèse pour exécuter la fonction anonyme */
alert('Premier tour de plusUn : ' + plusUn() +
      '\nDeuxième tour de plusUn : ' + plusUn() +
      '\nPremier tour de autreInstance : ' + autreInstance() +
      '\nTroisième tour de plusUn : ' + plusUn() );

Les objets navigateur

JavaScript permet d’accéder et de manipuler des parties de la fenêtre du navigateur elle-même. Par exemple, vous serez capable de changer l’URL présente dans la barre d’adresse, d’ouvrir ou de fermer des fenêtres du navigateur.

En JavaScript, le navigateur est l’objet window. Il dispose d’un certain nombre de propriétés et de fonctions.

propriétés / fonctionsDescription
eventReprésente l’état de l’événement
historyContient les URL sui ont été visitées dans cette fenêtre du navigateur
locationDonne accès en lecture etécriture à l’URL présente dans la barre d’adresse
statusModifie ou retourne le texte présent dans la barre de statut de la fenêtre
alert()Affiche une boite de dialogue avec le message spécifié et le bouton “OK”
close()Ferme la fenêtre courante
confirm()Affiche une boite de dialogue et les boutons “Annuler” et “OK”
focus()Positionne le focus sur la fenêtre courante

Exercices

Gestion d’un formulaire de contact

Nous souhaitons réaliser un formulaire de contact comprenant les champs suivant.

  • nom
  • prénom
  • adresse email
  • message

Nous souhaitons autoriser l’envoi du formulaire seulement si tous les champs sont remplis.

  1. Écrivez une fonction JavaScript “controleFormulaire()” assurant la vérification des champs et retournant “true” si tous les champs sont remplis, ou “false” sinon.
  2. Cette fonction sera appelée sur l’événement de la soumission du formulaire.
  3. Ajoutez, dans la fonction de contrôle, une variable (chaîne de caractères) contenant le message. Cette variable sera complétée avec les messages correspondant à chaque erreur. Avant de quitter la fonction, affichez le message s’il y a une ou plusieurs erreurs dans le paragraphe ayant l’identifiant : id="message_erreur« .
  4. Mettre en évidence les champs en erreur : modifier l’apparence d’un champ en erreur en affectant une classe css spécifique au champ, qui modifie alors son apparence.

Le code source HTML du formulaire est fourni pour exemple :

<!DOCTYPE html>
<html lang="fr">
<head>
  <title>Gestion d'un formulaire en JavaScript</title>
  <meta charset="utf-8" />
</head>
<body>

  <p id="message_erreur"></p>

  <form method="post" action="#" enctype="multipart/form-data" id="myForm">
    <p>
      <label for="nom">
        Nom
        <span class="alerte" title="champ obligatoire">*</span> 
        <input name="nom" id="nom" value="" type="text">
      </label>
    </p>
    <p>
      <label for="prenom">
        Prénom
        <span class="alerte" title="champ obligatoire">*</span>
        <input name="prenom" id="prenom" value="" type="text">
      </label>
    </p>
    <p>
      <label for="email" lang="en">
        Adresse mail 
        <span class="alerte" title="champ obligatoire">*</span>
        <input name="email" id="email" value="" type="text">
      </label>
    </p>
    <p>
      <label for="message">
        Message 
        <span class="alerte" title="champ obligatoire">*</span> 
        <textarea name="message" rows="10" id="message"></textarea>
      </label>
    </p>
    <p class="actions"><input name="envoyer" value="Envoyer" class="submit" type="submit"></p>
    <p class="alerte"><strong>* champs obligatoires </strong></p>
  </form>

</body>
</html>

Devinez le nombre

Dans cet exercice, nous allons voir comment construire le jeu simple que vous pouvez essayer ci-dessous:

Essayez de jouer et familiarisez-vous avec ce jeu avant de continuer.

Imaginons que vous disposiez des consignes suivantes pour créer ce jeu :

Je vous demande de créer un jeu simple de devinette de nombre. Le jeu choisit aléatoirement un nombre entre 1 et 100, puis il met le joueur au défi de le deviner en 10 tentatives maxi. À chaque tour, le joueur doit être informé s’il a deviné ou non le bon nombre — si ce n’est pas le cas, le jeu lui indique si son estimation est trop basse ou trop élevée. Le jeu doit également rappeler au joueur les nombres déjà proposés. Le jeu se termine quand le joueur a deviné le nombre mystère, ou s’il a épuisé ses 10 chances. A la fin du jeu, le joueur a la possibilité de débuter une nouvelle partie.

La première chose à faire en regardant ce résumé, est de le décomposer en tâches simples et codables comme le ferait un programmeur :

  1. Générer un nombre aléatoire entre 1 et 100.
  2. Stocker le nombre de tours déjà joués. Commencer par 1.
  3. Fournir au joueur le moyen de saisir un nombre.
  4. Stocker l’ensemble des propositions de nombres pour que le joueur puisse les consulter.
  5. Vérifier si le nombre saisi par le joueur est correct.
  6. S’il est correct :
    1. Afficher un message de félicitations.
    2. Empêcher que le joueur saisisse de nouveau un nombre.
    3. Afficher un contrôle pour que le joueur puisse rejouer.
  7. S’il est faux et que le joueur a encore des tours à jouer :
    1. Informer le joueur que sa proposition de nombre est fausse.
    2. Lui permettre d’entrer une nouvelle proposition de nombre.
    3. Incrémenter le nombre de tours de 1.
  8. S’il est faux et que le joueur n’a plus de tours à jouer :
    1. Informer le joueur qu’il a perdu et que la partie est finie.
    2. Empêcher que le joueur saisisse de nouveau un nombre.
    3. Afficher un contrôle pour que le joueur puisse rejouer.
  9. Une fois le jeu redémarré, s’assurer que la logique du jeu et l’interface utilisateur sont complètement réinitialisées, puis revenir à l’étape 1.

Calculatrice

Dans cet exercice, on va essayer de créer une calculatrice qui exécute les opérations basiques, à savoir, addition, soustraction, multiplication et division. La page contiendra trois zones de texte qui représenterons respectivement: nombre 1, nombre 2 et résultat de l’opération, ainsi que 4 boutons qui représenteront les 4 opérations prévues.

En tout, notre page ressemblerait à ceci :