LP
IMApp

Scripts
Clients

Leçon 06

Leçon 06 : Introduction aux fonctions

Contenu :

Cette leçon présente les fonctions, concept clef de la programmation en général, et de la programmation JavaScript en particulier. Après avoir précisé la syntaxe des fonctions, on tente de comprendre le modèle d'exécution qui devient plus complexe.

Objectifs :

Etre capable de définir des fonctions simples, comprendre l'intérêt et la nécessité de factoriser le code à l'aide de fonctions dans des exemples simples.

Exemple et motivation

Reprenons le sujet de l'exercice 5 de la leçon 2 et supposons que l'on veuille introduire des tests dans ce petit programme pour s'assurer que les données saisies soient bien des nombres. Le nouveau code ressemble alors ça :

var saisie = prompt( "Nombre de parts par convive ?","" );
while ( isNaN( saisie ) )
	saisie  = prompt( "Mauvais nombre, recommencez !","" );
var parts_par_convive = Number( saisie );

saisie = prompt( "Nombre de convives ?","" );
while ( isNaN( saisie ) )
	saisie  = prompt( "Mauvais nombre, recommencez !","" );
var nombre_de_convives = Number( saisie );

saisie = prompt( "Nombre de parts par pizza ?","" );
while ( isNaN( saisie ) )
	saisie  = prompt( "Mauvais nombre, recommencez !","" );
var parts_par_pizza = Number( saisie );

alert( "Vous devez commander " +
        Math.ceil( parts_par_convive * nombre_de_convives / parts_par_pizza ) +
        " pizzas" );
Remarquez que dans ce programme, on répète trois fois la même boucle while. Cette répétition est néfaste pour plusieurs raisons : La solution à ces différents problèmes est l'usage d'une fonction : on va donner un nom à la portion de code qui se répète et invoquer ensuite ce code via ce nom. C'est un peu comme utiliser le nom d'une personne plutôt que la description de cette personne ! Avec l'introduction d'une fonction, notre code devient :
function tester() {
	while ( isNaN( saisie ) )
		saisie  = prompt( "Mauvais nombre, recommencez !","" );	
}

var saisie = prompt( "Nombre de parts par convive ?","" );
tester();
var parts_par_convive = Number( saisie );

saisie = prompt( "Nombre de convives ?","" );
tester();
var nombre_de_convives = Number( saisie );

saisie = prompt( "Nombre de parts par pizza ?","" );
tester();
var parts_par_pizza = Number( saisie );

alert( "Vous devez commander " +
        Math.ceil( parts_par_convive * nombre_de_convives / parts_par_pizza ) +
        " pizzas" );
Dans le code précédent, la fonction tester est une nouvelle action disponible dans le programme. Cette action est définie une fois mais peut être exécutée plusieurs fois. Pour définir une fonction, on utilise le mot-clef function suivi du nom de la fonction, suivi d'une paire de parenthèses et enfin du corps de la fonction délimité par deux accolades.

Paramètres

Dans le code précédent, on constate que les trois appels à la fonction tester sont précédés de la même instruction saisie = prompt(...). Dans ces instruction, seule la chaîne de caractères paramètre de la fonction prompt change. Puisque ces instructions sont presque identiques, on peut les factoriser en plaçant l'instruction saisie = prompt(...) dans la fonction tester. La chaîne de caractères paramètre de la fonction prompt doit changer à chaque appel de la fonction et en conséquence, cette chaîne doit être un paramètre de la fonction :

function saisir( message ) {
	saisie = prompt( message );
	while ( isNaN( saisie ) )
		saisie  = prompt( "Mauvais nombre, recommencez !","" );	
}

var saisie;

saisir( "Nombre de parts par convive ?" );
var parts_par_convive = Number( saisie );

saisir( "Nombre de convives ?" );
var nombre_de_convives = Number( saisie );

saisir( "Nombre de parts par pizza ?" );
var parts_par_pizza = Number( saisie );

alert( "Vous devez commander " +
        Math.ceil( parts_par_convive * nombre_de_convives / parts_par_pizza ) +
        " pizzas" );
Remarquez qu'on a changé le nom de la fonction pour l'adapter à son nouveau rôle : maintenant, la fonction s'appelle saisir car elle permet de saisir une valeur fournie par l'utilisateur, de tester cette valeur et lorsque cette valeur est un nombre, de l'affecter à la variable saisie. Bien entendu, le nom d'une fonction peut être absolument quelconque, mais comme pour les variables, il est important de choisir un nom significatif qui corresponde bien au rôle de la fonction. Maintenant, la fonction saisir a un paramètre représenté par la variable message placée entre les parenthèses qui suivent le nom de la fonction. Le paramètre est une information manquante dont la fonction a besoin pour s'exécuter et remplir son rôle. On peut voir le paramètre comme une variable locale à la fonction : cette variable n'existe que dans le contexte de la fonction, elle est donc inaccessible à l'extérieur de la fonction (dans le reste du programme). A l'inverse, la variable saisie est une variable globale pour la fonction, car elle est définie à l'extérieur de la fonction. Une différence notable entre le paramètre message et la variable globale saisie concerne l'existence même de ces variables : Une fonction peut avoir un nombre quelconque de paramètres. Par exemple, on peut modifier la fonction saisir pour qu'elle contrôle si son paramètre est bien un nombre, et s'il est compris dans un intervalle donné :
function saisir( message, min, max ) {
	saisie = prompt( message );
	while ( isNaN( saisie ) || saisie < min || saisie > max )
		saisie  = prompt( "Mauvais nombre, recommencez !","" );	
}

var saisie;

saisir( "Nombre de parts par convive ?", 1, 5 );
var parts_par_convive = Number( saisie );

saisir( "Nombre de convives ?", 2, 10 );
var nombre_de_convives = Number( saisie );

saisir( "Nombre de parts par pizza ?", 4, 6 );
var parts_par_pizza = Number( saisie );

alert( "Vous devez commander " +
        Math.ceil( parts_par_convive * nombre_de_convives / parts_par_pizza ) +
        " pizzas" );
Remarquez que dans le test de la boucle while, on compare directement la valeur de la variable saisie (une chaîne de caractères) avec un nombre (les paramètres min et max). Dans cette situation JavaScript tente de convertir la chaîne de caractères en nombre, ce qui dans ce cas va réussir puisqu'on est sûr que la chaîne est bien un nombre (pourquoi ?).

Valeur de retour

En regardant de près l'utilisation de la fonction saisir précédente, on voit qu'elle ne fait qu'affecter la chaîne de caractères saisie à la variable saisie, après avoir vérifié que cette chaîne était bien un nombre positif. On peut aller plus loin et donner à la fonction un rôle plus complet et surtout plus abstrait en lui demandant de retourner le nombre saisi (bien sûr après avoir vérifié la chaîne de caractères saisie par l'utilisateur). Pour celà, la fonction doit maintenant retourner une valeur, un peu à la façon d'une fonction mathématique comme le sinus. On modifie la fonction et le code de la façon suivante :

function saisir( message, min, max ) {
	var saisie = prompt( message );
	while ( isNaN( saisie ) || saisie < min || saisie > max )
		saisie  = prompt( "Mauvais nombre, recommencez !","" );	
        return Number( saisie );
}

var parts_par_convive = saisir( "Nombre de parts par convive ?", 1, 5 );

var nombre_de_convives = saisir( "Nombre de convives ?", 2, 10 );

var parts_par_pizza = saisir( "Nombre de parts par pizza ?", 4, 6 );

alert( "Vous devez commander " +
        Math.ceil( parts_par_convive * nombre_de_convives / parts_par_pizza ) +
        " pizzas" );
Maintenant, la fonction saisir produit une valeur (un nombre) ! Autrement dit, l'appel de la fonction est une expression qui produit une valeur. Remarquez un point très important : la variable saisie est maintenant locale à la fonction ! En effet, il n'est plus nécessaire que le programme voit cette variable puisque tout le processus de saisie, de vérification et de production du nombre est maintenant localisé dans la fonction. La variable saisie est locale à la fonction car elle est déclarée (à l'aide du mot-clef var) dans le corps de la fonction.

Fonctions et tableaux

Les tableaux peuvent être passés en paramètre aux fonctions. Supposons par exemple qu'on désire calculer des moyennes de notes fournies sous la forme de tableaux de notes (par exemple, un tableau par étudiant). On peut écrire la fonction moyenne suivante :

function moyenne( notes ) {
  var somme = 0;
  for ( var i = 0; i < notes.length; i++ )
    somme += notes[i];
  return somme / notes.length;
}
Cette fonction prend en paramètre un tableau contenant des nombres, calcule et retourne le moyenne de ces nombres. Remarquez que grâce à l'attribut length, on a pas besoin de connaîte effectivement la taille du tableau ! Si on désire utiliser cette fonction dans un programme qui lit interactivement les notes via la fonction prompt il faut prévoir une fonction qui convertit des chaînes de caractères stockées dans un tableau en nombres. C'est le rôle de la fonction convertir :
function convertir( chaines ) {
  for ( var i = 0; i < chaines.length; i++ )
    chaines[i] = Number( chaines[i] );
}
Le paramètre chaines de la fonction convertir est un tableau contenant des chaînes de caractères, chacune d'elle représentant un nombre. On peut maintenant à l'aide des fonctions chaines et moyenne écrire un programme qui demande à l'utilisateur d'entrer une suite de notes et qui affiche la moyenne de ces notes :
var chaine  = prompt( "Entrez les notes séparées par exactement un espace :","");
var notes = chaine.split( " " );

convertir( notes );

alert( "La moyenne est " + moyenne( notes ) );
Notez que la variable du programme qui recueille le tableau de chaînes de caractères et le paramètre de la fonction moyenne ont le même nom (notes), ce qui bien sûr n'est absolument pas nécessaire : le noms des paramètres d'une fonction peuvent être absolument quelconque et ne font jamais référence à des variables déclarées à l'extérieur de la fonction !
Dans cette version, la fonction convertir agit sur son paramètre (un tableau) et convertit chaque élément (une chaîne de caractères) en nombre. Une fonction pouvant retourner un tableau, on peut modifier la fonction convertir de façon qu'elle :
function convertir( chaines ) {
  var notes = [];
  for ( var i = 0; i < chaines.length; i++ )
    notes.push( Number( chaines[i] ) );
  return notes;
}
Dans cette nouvelle version, la variables notes devient locale à la fonction, et contient un nouveau tableau distinct du tableau paramètre chaines. Avec cette nouvelle version, le programme devient :
var chaine  = prompt( "Entrez les notes séparées par exactement un espace :","");

alert( "La moyenne est " + moyenne( convertir( chaine.split( " " ) ) ) );
Remarquez que cette dernière version parait très simple, pour peu qu'on dispose des deux fonctions convertir et moyenne. On peut imaginer ces fonctions comme faisant partie de JavaScript ou tout au moins d'une librairie de fonctions utiles qu'on pourrait utiliser au besoin. C'est là le coeur même de l'activité de programmation web : développer des fonctions générales et utiles qu'on pourra réutiliser dans plusieurs projets !

A partir de cette leçon et pour une grande partie des exercices à venir, un squelette de solutions va vous être proposé. Pour cette leçon, ces squelettes consistent en des fonctions vides dont vous devez comprendre le rôle et que vous devez ensuite compléter. Si un squelette ne vous inspire pas et que vous pensez à une solution différente, vous pouvez bien sûr opter pour votre solution.

Exercice 1

Reprenez l'exercice 6 de la leçon 2, mais cette fois le prénom et le nom seront normalisés de la même façon (toutes les lettres en minuscule sauf la première) par la fonction normaliser.

Fichier(s)

exo1.js

Exercice 2

Reprenez l'exercice 1 de la leçon 3 en normalisant les prénoms et les noms à l'aide de la fonction normaliser de l'exercice précédent, et en utilisant la fonction bienvenue pour fabriquer le message de bienvenue.

Fichier(s)

exo2.js

Exercice 3

Reprenez l'exercice 1 de la leçon 5 en utilisant maintenant deux fonctions jour et mois pour retourner respectivement le nom du jour de la semaine et le nom du mois en fonction d'un entier compris entre 0 et 6 pour le jour, et compris entre 0 et 11 pour le mois. Les fonctions jour et mois retournent une chaîne de caractères adéquate pour signaler que leur paramètre est erroné.

Fichier(s)

exo3.js

Exercice 4

Reprenez l'exercice 2 de la leçon 2 en utilisant maintenant deux fonctions moyenne et saisirNombre : la fonction moyenne calcule et retourne la moyenne de ses trois paramètres, et saisirNombre lit la chaÎne de caractères entrée par l'utilisateur et retourne le nombre correspondant. Tant que la chaîne fournie par l'utilisateur n'est pas un nombre, la fonction lui demande une nouvelle chaîne.

Fichier(s)

exo4.js

Exercice 5

Reprenez l'exercice 4 de la leçon 3 en rèutilisant les deux fonctions moyenne et saisirNombre de l'exercice précédent, et en utilsant la fonction appreciation qui retourne l'appréciation adéquate en fonction de la note (son paramètre).

Fichier(s)

exo5.js

Exercice 6

Reprenez l'exercice 4 de la leçon 4 en modifiant la fonction saisirNombre des exercices précédent, et en utilsant la fonction table qui retourne sous forme d'une chaîne de caractères, la table de mutliplication de son paramètre. La fonction saisirNombre permet maintenant de saisir un nombre compris dans un intervalle donné et elle a maintenant deux paramètres, les bornes de cet intervalle.

Fichier(s)

exo6.js

Exercice 7

Reprenez l'exercice 8 de la leçon 4 en modifiant une fois encore la fonction saisirNombre des exercices précédent, et en utilsant la fonction premier qui teste si son paramètre est un nombre premier. La fonction saisirNombre permet maintenant de saisir un nombre positif ou nul, et elle n'a plus de paramètre.

Fichier(s)

exo7.js

Exercice 8

Reprenez l'exercice 5 de la leçon 5 en utilisant les fonctions suivantes :
  • saisirNombre : c'est la même fonction que dans les exercices 4 et 5
  • premiers : c'est la fonction principale de cet exerice. Elle prend un entier k en paramètre et retourne les k premiers nombres premiers sous la forme d'un tableau de taille k
  • pasDivisible : c'est une fonction utilitaire qui est utilisée par la fonction précédente. Elle prend en paramètre un entier n et un tableau d'entiers (premiers) t et teste si n est divisible par un des entiers contenus dans t
  • resultats : c'est une fonction utilitaire qui sert à présenter les nombres premiers calculés. Elle prend en paramètre un tableau d'entiers (premiers) et retourne une chaîne de caractères présentant la liste de ces entiers à raison de 10 entiers par ligne

Fichier(s)

exo8.js

Exercice 9

Reprenez l'exercice 4 de la leçon 5. Maintenant, le programme doit afficher, à la demande l'utilisateur, plusieurs tirages successifs. Pour chaque tirage, le programme doit afficher les 5 numéros du loto triés par ordre croissant. Pour réaliser ce tri, vous devez utiliser la méthode sort d'une façon spéciale : cette méthode va être appliquée à un tableau d'entiers et va prendre en paramètre une fonction de comparaison entre ces entiers, qui va être utilisée pour les comparer entre eux durant le tri. Reportez-vous à la documentation pour comprendre comment utiliser sort de cette façon.

Fichier(s)

exo9.js

Exercice 10

Reprenez l'exercice 2 de la leçon 4. Maintenant, il est possible de jouer plusieurs parties à la suite : à la fin d'une partie, le programme demande à l'utilisateur s'il veut en jouer une autre. A la fin du jeu (après que toutes les parties aient été jouées), le programme affiche des résultats comprenant :
  • le nombre total de parties jouées
  • le nombre moyen d'essais sur toutes les parties jouées
  • le nombre d'essais de la meilleure partie (de la partie dans laquelle l'utilisateur a deviné le nombre avec le moins d'essais)
Contrairement aux exercices précédents, aucun squelette n'est fourni pour cet exercice et vous devez le concevoir entièrement. A titre indicatif, la solution de référence contient quatre fonctions, plus le code du programme principal.