Logo JavaScript

Chapitre 3: Types avancés

Objectifs:

  • Savoir développer le Jeu du Pendu, et un répertoire téléphonique
  • Utilisation de tableaux et d'objets

Plan du chapitre:

  1. Tableaux, introduction (Slides du TP)
    • Application: Calendrier
  2. Tableaux, fonctions avancées
    • Application: Jeu du Pendu
  3. Objets JavaScript (Slides du TP)
    • Application: Annuaire téléphonique
  4. Objets, usage avancé
    • Application: Répertoire téléphonique

1. Tableaux, introduction

Un tableau (appelé array en anglais) est un type avancé de valeurs. C'est une liste ordonnée de valeurs.

Exemple:

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];

Caractéristiques

Comme tous les autres types de valeur, un tableau peut être stocké dans une variable.

En revanche, en JavaScript, les tableaux sont considérés comme une forme particulière du type object. Autrement dit, typeof [] retourne "object".

Comme toute variable en JavaScript, les valeurs stockées dans un tableau peuvent être de n'importe quel type (y compris un tableau), et n'ont pas besoin d'être tous du même type:

var fourreTout = [ null, true, 'bonjour', 1.2, fruits, undefined ];

Tout tableau possède une propriété length qui vaut le nombre de valeurs qu'il contient:

fourreTout.length; // => retourne 6, car il y a 6 valeurs dans notre tableau

Accéder aux éléments d'un tableau

Chaque élément d'un tableau est numéroté par ce qu'on appelle un "indice" (ou "index" en anglais).

A noter que cette numérotation commence par 0 (zéro), et non par 1.

Du coup, l'indice du premier élément est 0, et l'indice du dernier élément est length - 1 (length étant le nombre d'éléments du tableau).

On peut accéder à un élément de tableau en précisant son indice entre crochets:

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];
fruits[0]; // vaut 'Mangue'
fruits[1]; // vaut 'Raisin'
fruits[2]; // vaut 'Figue'
fruits[3]; // undefined

Modification d'un élément

Pour modifier la valeur d'un élément, il suffit de l'adresser par indice (comme vu juste avant) puis de lui affecter une valeur, comme on le ferait pour une variable:

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];
fruits[1] = 'Kiwi';
fruits; // => vaut [ 'Mangue', 'Kiwi', 'Figue' ]

Ajout d'élément

La méthode push() permet d'ajouter un élément à la fin du tableau. La valeur de cet élément est à passer en paramètre (entre parenthèses) de la méthode, quand on l'appelle:

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];
fruits.push('Banane'); // appel de la méthode push() sur le tableau fruits, avec `Banane` en paramètre
fruits; // => [ 'Mangue', 'Raisin', 'Figue', 'Banane' ]

À chaque fois qu'on ajoute un élément à un tableau en appelant la méthode push(), sa longueur length est incrémentée (c.a.d. sa valeur augmente de 1):

fruits.length; // => 4, désormais

Retrait d'élément

La méthode pop() retourne la dernière valeur du tableau puis la retire de ce tableau:

var fruits = [ 'Mangue', 'Raisin', 'Figue' ];
var f = fruits.pop();
f;      // => `Figue`
fruits; // => [ 'Mangue', 'Raisin' ];

À chaque fois qu'on retire un élément d'un tableau en appelant la méthode pop(), sa longueur length est décrémentée (c.a.d. sa valeur décroit de 1):

fruits.length; // => 2, désormais

Pratique: Calendrier

var jours = [ 'lun', '007', 'mer', 'jeu', 'ven', 'sam', 'BUG' ];

Quelles instructions JavaScript faut-il exécuter pour effectuer les opérations suivantes ?

  1. Retirer le dernière valeur du tableau jours
  2. Afficher les valeurs du tableau dans la console
  3. Ajouter la valeur 'dim' à la fin du tableau
  4. Remplacer la valeur '007' par 'mar'
  5. Afficher le nombre de valeurs du tableau dans la console
  6. Afficher la troisième valeur du tableau dans la console

Quelle est la valeur finale du tableau, après avoir effectué toutes ces opérations ?

Solution


2. Tableaux, fonctions avancées

Recherche d'élément par valeur

La méthode indexOf() retourne l'indice du premier élément d'un tableau, pour une valeur donnée. La valeur recherchée est à passer en paramètre (entre parenthèses) de la méthode, quand on l'appelle:

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Raisin' ];
fruits.indexOf('Raisin'); // => 1

Si aucun élément du tableau ne contient la valeur cherchée, l'appel à indexOf() retourne -1:

fruits.indexOf('Pomme'); // => -1, car il n'y a pas de valeur 'Pomme' dans fruits

Concaténation de tableaux

Comme pour les chaînes de caractères, il est possible de concaténer des tableaux.

La concaténation de deux tableaux consiste à créer un nouveau tableau contenant les éléments de ces deux tableaux.

La méthode concat() retourne un nouveau tableau contenant les éléments du tableau sur lequel elle est appelée, et d'un autre tableau passé en paramètre:

var fruits1 = [ 'Mangue', 'Raisin' ];
var fruits2 = [ 'Figue', 'Kiwi' ];
fruits1.concat(fruits2); // => [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ]

Il est bien entendu possible d'utiliser des tableaux littéraux, au lieu de variables:

[ 'Mangue', 'Raisin' ].concat([ 'Figue', 'Kiwi' ]); // => [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ]

Partitionnement de tableaux

La méthode slice() retourne un nouveau tableau contenant un extrait du tableau sur lequel elle est appelée.

A chaque appel de cette méthode, il faut fournir deux paramètres: l'indice de l'élément où commence cet extrait, et l'indice de l'élément où se termine l'extrait (non compris):

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];
fruits.slice(1, 3); // => [ 'Raisin', 'Figue' ]

Altération de tableau

La méthode splice() (à ne pas confondre avec slice()) permet à la fois de supprimer et d'insérer des éléments dans un tableau, en fournissant leur indice(s).

Syntaxe d'appel de la méthode: tableau.splice(i, c, v1, v2, ...), avec

  • paramètre i: indice à partir duquel on va effectuer la modification
  • paramètre c: nombre d'éléments à supprimer depuis l'indice i
  • paramètre(s) suivant(s): valeur(s) d'élément(s) à insérer à partir de l'indice i

Voici un exemple utilisant les paramètres i et c: (suppression seulement)

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];
fruits.splice(1, 2); // depuis l'indice 1, supprimer 2 valeurs
fruits; // => [ 'Mangue', 'Kiwi' ]

Voici un exemple utilisant tous les paramètres: (insertion seulement)

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];
fruits.splice(1, 0, 'Pomme'); // depuis l'indice 1, supprimer 0 valeurs, puis y insérer 'Pomme'
fruits; // => [ 'Mangue', 'Pomme', 'Raisin', 'Figue', 'Kiwi' ]

Contrairement aux méthodes concat() et slice(), splice() ne retourne pas un nouveau tableau, mais modifie le tableau sur lequel elle est appelée.

Pratique: Épicerie

var fruits = [ 'Mangue', 'Raisin', 'Figue', 'Kiwi' ];

Écrire un programme qui:

  1. Affiche la liste de fruits disponibles;
  2. Demande au client quel fruit il désire acheter:
    • s'il est présent dans le tableau fruits: le retirer du tableau, et afficher 'ok!',
    • sinon, afficher 'indisponible...'.
  3. Affiche à nouveau la liste de fruits disponibles.

Solution

Exercice: Jeu du Pendu

Principe du jeu: Le joueur doit deviner un mot, lettre par lettre. Il perd après 10 mauvaises tentatives.

Nous allons utiliser les variables suivantes:

var tentatives = 10;
var lettres = [ 's', 'u', 'p', 'e', 'r' ];

À chaque tentative, le joueur va proposer une lettre, puis:

  • si la lettre fait partie du mot à deviner (dont les lettres sont stockées dans le tableau lettres), cette lettre sera supprimée du tableau;
  • sinon, le nombre de tentatives décrémente.

Le joueur gagne la partie quand il a deviné toutes les lettres du mot. (c.a.d. le tableau lettres ne contient plus aucun élément)

Le joueur perd la partie si le nombre de tentatives atteint la valeur 0.

Exercice: implémenter le jeu du pendu en JavaScript.

Mots clés à utiliser: alert, prompt, if, else, =, ===, indexOf, splice, et for

Conseil: avant de créer une boucle for, implémenter l'algorithme qui sera exécuté à chaque itération de cette boucle. (une itération = une tentative du joueur)

Étapes proposées:

  1. Afficher le nombre de lettres à trouver, et de tentatives restantes.
  2. Si tentatives est nul, afficher perdu!.
  3. Si le nombre de lettres restantes à trouver est nul, afficher bravo!.
  4. Demander une lettre au joueur, en utilisant prompt().
  5. Si la lettre fait partie du tableau lettres, la supprimer du tableau. Sinon décrémenter tentatives. Afficher un message au joueur dans chaque cas.
  6. Créer une boucle for permettant de jouer jusqu'à la fin de partie (perdu ou bravo).

BONUS: Dans le cas où le joueur devine une lettre qui apparaît plus d'une fois dans le mot, retirer toutes les occurrences de cette lettre en une seule fois.

Solution + Bonus


3. Objets JavaScript

Dans la première moitié de ce chapitre nous avons vu que les tableaux permettaient de stocker une liste ordonnées d'éléments (ayant chacun une valeur de n'importe quel type). Et que ces éléments étaient indexés par des numéros.

Exemple:

var tableau = ['a', 'b', 4];
// => Correspondance entre numéros d'éléments et leur valeur
//    0: 'a'
//    1: 'b'
//    2: 4

Les objets permettent aussi de stocker des valeurs.

Alors que chaque valeur d'un tableau est stockée dans un élément, chaque valeur d'un objet est stockée dans ce qu'on appelle une propriété.

Alors que chaque élément d'un tableau est indexé par un numéro (indice), chaque propriété d'un objet est indexé par une chaîne de caractères.

Définition d'objet

Contrairement à la manière de définir un tableau, chaque valeur de propriété stockée dans un objet doit être précédée du nom de cette propriété (aussi appelé clé, ou key en anglais), et suivi par : (deux points).

var objet = {
  prop1: 'a',
  prop2: 'b',
  prop3: 4,
};
// => Correspondance entre noms de propriétés et leur valeur
//    'prop1': 'a'
//    'prop2': 'b'
//    'prop3': 4

À noter que chaque définition de propriété doit être ponctuée d'une virgule.

Les règles d'indentation s'appliquent aussi à la définition d'objets sur plusieurs lignes, car les propriétés sont définies entre accolades.

Le format JSON (JavaScript Object Notation, utilisé très couramment par les APIs de sites web, et exports structurées de données depuis le web) correspond à cette manière de définir des objets. Nous allons y revenir plus tard dans ce chapitre.

Accès aux propriétés d'un objet

Il existe deux manière d'accéder à la valeur d'une propriété d'objet:

  1. La notation pointée, ex: objet.prop
  2. L'usage des crochets, ex: objet[prop]

Comme pour les tableaux, l'adressage par crochets a un avantage intéressant: il permet d'accéder à un élément dont la clé est stockée dans une variable.

Exemple:

var objet = {
  prop1: 'a',
  prop2: 'b',
  prop3: 4,
};

// adressage littéral par notation pointée:
objet.prop2;        // => 'b'

// adressage littéral par crochets:
objet['prop2'];     // => 'b'

// adressage symbolique par crochets:
var cle = 'prop2';
objet[cle];         // => 'b'

La notation pointée est plus concise et lisible, mais impose de mentionner la clé "en dur" (littéralement) dans le code:

// adressage littéral, avec notation pointée:
objet.prop2; // => 'b'

// adressage symbolique, impossible avec notation pointée:
var cle = 'prop2';
objet.cle;   // => undefined, car il n'y a pas de propriété appelée 'cle' dans l'objet

Modification de propriétés

La modification d'une propriété d'objet fonctionne similairement à la modification d'un élément de tableau: il suffit d'effectuer une affectation après avoir adressé la valeur à modifier.

var tableau = ['a', 'b', 4];
tableau[1] = 3; // => tableau = ['a', 3, 4]

var objet = {
  prop1: 'a',
  prop2: 'b',
  prop3: 4,
};
objet.prop2 = 3; // => objet: { prop1: 'a', prop2: 3, prop3: 4 }

Il est bien sûr possible d'utiliser indifféremment l'adressage par notation pointée ou par crochets, pour une affectation.

Clés riches

Contrairement aux restrictions imposées pour nommer les variables en JavaScript, il est possible d'inclure des espaces et des caractères spéciaux dans les noms de propriétés.

Par contre, il faut dans ce cas utiliser des apostrophes (ou guillemets) pour définir et adresser ces propriétés:

var mesAmis = {
  'Luke Skywalker': true,
  'Dark Vador': false,
};
mesAmis.LukeSkywalker;     // => undefined
mesAmis.Luke Skywalker;    // => /!\ syntax error
mesAmis['Luke Skywalker']; // => true

Notes sur les types

Comme pour les tableaux, la valeur d'une propriété d'objet peut être de n'importe quel type: string, number, boolean, null, undefined, object ou function.

A noter que, comme les objets, les tableaux sont aussi de type object.

typeof { prop: 'a' };  // => "object"
typeof [ 'a', 3, 4 ];  // => "object"

Mais les tableaux ont des caractéristiques particulières que n'ont pas les objets. Notamment l'indexation numérique, des fonctions spécifiques comme slice, etc...

Mise en pratique: Annuaire téléphonique

Développer un programme qui permet d'afficher le numéro de téléphone d'un ami (à l'aide de alert), en saisissant son nom (à l'aide de prompt).

Afin que l'annuaire soit extensible à l'avenir, aucune condition if ne doit être utilisée. Nous allons donc stocker les noms et numéros de téléphones dans un objet, et utiliser l'adressage par crochets pour trouver un numéro à partir d'un nom.

Solution


4. Objets, usage avancé

Hiérarchie d'objets

Comme il est possible de stocker un objet comme valeur d'une propriété d'objet, cela veut dire que nous pouvons définir des hiérarchies / arbres d'objets.

Exemple:

var compteFacebook = {
  amis: {
    'Luke Skywalker': true,
    'Dark Vador': false,
  },
  groupes: {
    maitresJedi: {
      titre: 'Groupe des maîtres Jedi',
      membres: [ 'Yoda', 'Obi Wan' ],
    },
    lolcats: {
      titre: 'Vive les chats !',
      membres: [ 'Patrick' ],
    },
  },
};

Dans l'exemple ci-dessus, nous avons jusqu'à trois niveaux d'imbrication d'objets (voire quatre, si on compte les tableaux comme des objets): l'objet compteFacebook contient une propriété groupes (de type objet) contenant une propriété maitresJedi (aussi de type objet) contenant deux propriétés (de type chaîne de caractères et tableau).

Cet objet peut être représenté visuellement sous forme d'un arbre.

Pour accéder au premier élément du tableau membres de la sous-propriété maitresJedi, on doit écrire le cheminement à suivre, de propriété à sous-propriétés.

Exemple:

// adressage par notation pointée:
compteFacebook.groupes.maitresJedi.membres[0]; // => 'Yoda'

// adressage par crochets:
compteFacebook['groupes']['maitresJedi']['membres'][0]; // (idem)

Dans cet exemple aussi, on peut utiliser indifféremment la notation pointée ou les crochets, pour accéder à la propriété d'un objet.

Énumérer les propriétés d'un objet

Il existe deux manières d'énumérer les propriétés d'un objet:

  1. utiliser une boucle for-in;
  2. ou itérer sur le tableau des clés de l'objet, à l'aide de la fonction Object.keys().

Une boucle for-in s'utilise de cette façon:

var mesAmis = {
  'Luke Skywalker': true,
  'Dark Vador': false,
};
for (var cle in mesAmis) {
  console.log(cle, '->', mesAmis[cle]);
}
// => lignes affichées dans la console:
//    Luke Skywalker -> true
//    Dark Vador -> false

Contrairement à une boucle for classique (dans laquelle on définit une instruction d'initialisation, une expression conditionnelle et une instruction d'incrémentation, séparés par des points-virgules, pour rappel), la boucle for-in va répéter la liste d'instructions entre accolades pour chaque clé de l'objet.

Dans notre exemple de code ci-dessus, la variable cle prendra donc la valeur d'une clé de l'objet mesAmis, pour chacune de ses propriétés.

A noter que cela fonctionne aussi sur les tableaux, sauf que l'indice de chaque élément du tableau sera fourni (au lieu de la clé de chaque propriété de l'objet).

Énumérer les propriétés d'un objet, une autre manière

Lorsqu'elle est appelée en passant un objet en paramètre, la fonction Object.keys() retourne un tableau contenant les clés/noms de chaque propriété de cet objet.

var mesAmis = {
  'Luke Skywalker': true,
  'Dark Vador': false,
};
var cles = Object.keys(mesAmis); // => [ 'Luke Skywalker', 'Dark Vador' ]

On peut alors utiliser une boucle for plus classique pour itérer sur les valeurs de ce tableau:

for (var i = 0; i < cles.length; i++) {
  var cle = cles[i];
  console.log(i, ':', cle, '->', mesAmis[cle]);
}
// => lignes affichées dans la console:
//    0, Luke Skywalker -> true
//    1, Dark Vador -> false

Suppression d'une propriété

Pour supprimer une propriété d'un objet, il faut utiliser le mot clé delete de la manière suivante:

var objet = {
  prop1: 'a',
  prop2: 'b',
  prop3: 4,
};
delete objet.prop2;
// => objet === { prop1: 'a', prop3: 4 }

Exercice: Répertoire téléphonique

Sur la base de l'annuaire téléphonique réalisé plus haut, développer un programme qui propose les fonctionnalités suivantes:

  • recherche et affichage d'un numéro de téléphone en saisissant un nom,
  • liste des contacts de l'annuaire (nom + numéro de téléphone, à afficher dans la console à l'aide d'une boucle for..in),
  • ajout d'un contact (nom + numéro de téléphone) dans l'annuaire,
  • suppression d'un contact.

Pour permettre à l'utilisateur de choisir la fonctionnalité qu'il souhaite utiliser, lui demander de saisir la première lettre de la fonction:

  • 'r' pour rechercher,
  • 'l' pour afficher la liste,
  • 'a' pour ajouter,
  • et 's' pour supprimer.

Afin que l'utilisateur puisse utiliser plusieurs fonctionnalités d'affilée, placer le code du programme dans une boucle qui ne s'arrêtera que lorsque l'utilisateur aura tapé q au lieu de choisir une fonctionnalité.

Solution

results matching ""

    No results matching ""