User Tools

Site Tools


javascript:eloquent_javascript:functions

Chapter 3: Functions

  People think that computer science is the art of geniuses but the actual reality is the opposite, just many people doing things that build on each other, like a wall of mini stones.
  Donald Knuth

Vous avez vu les valeurs fonction, comme alert, et comment les appeler. Les fonctions sont la base de la programmation JavaScript. Le concept d'intégrer un morceau de programme dans une valeur a de nombreux usages. C'est un outil pour structurer des programmes pluslarges, pour réduire la répétition, pour associer des noms a des sous-programmes, et pour les isoler les uns des autres.

L'application la plus évidente des fonctions est la définition d'un nouveau vocabulaire. En langue naturelle, créer de nouveaux mots, des néologismes, dans une prose habituelle, humaine, est normalement considérée comme une erreur de style. Mais en programmation, c'est indispensable.

Un locuteur typique de l'anglais a un vocabulaire d'environ 20 000 mots. Peu de langages de programmation sont fournis avec 20 000 commandes. Et le vocabulaire qui est disponible tend à être défini précisemment, et donc à être moins flexible que le langage humain. Donc, l'on doit habituellement définir son propre vocabulaire pour éviter d'avoir à se répéter trop souvent.

Définir une fonction

Une définition de fonction est simplement une définition de variable normale ou la valeur donnée se trouve être une fonction. Par exemple, le code suivant défini la variable square pour qu'elle réfère a la fonction qui produit le carré du nombre donné :

var square = function(x) {
  return x * x;
};

console.log(square(12));
// → 144

Une fonction est créé par un expression qui commence avec le mot-clé function. Les fonctions ont un ensemble de paramètres (dans ce cas, seulement x) et un corps, qui contient les instructions qui doivent être utilisées lorsque la fonction est appelée. Le corps de la fonction doit toujours être entre des accolades (comme dans l'exemple précédent).

Une fonction peut avoir de multiples paramètres ou pas de paramètre du tout. Dans l'exemple suivant, makeNoise ne list aucun paramètre, tandis que poweren liste deux :

var makeNoise = function() {
  console.log("Pling!");
};

makeNoise();
// → Pling!

var power = function(base, exponent) {
  var result = 1;
  for (var count = 0; count < exponent; count++)
    result *= base;
  return result;
};

console.log(power(2, 10));
// → 1024

Certaines fonctions produisent une valeur, comme power et square, et certaines non, comme makeNoise, qui ne produit qu'un side effect. Une instruction return détermine la valeur que la fonction renvoie. When control comes across such a statement, il saute immédiatement hors de la fonction courante et renvoie cette fonction passe la valeur renvoyée au code qui avait appelé la fonction. Le mot-clé return keyword sans expression provoquera le renvoi de la valeur undefined.

Paramètres et scopes

Les paramètres donnés a une fonction fonctionnent comme des variables normales, mais leur valeur initiale est donné par le caller de la fonction, pas par le code de la fonction lui-même.

Une propriété importante des fonctions est que les variables créés à l'intérieur de celle-ci, incluant leur paramètres, sont locales à la fonction. Cela signifie, par exemple, que la variable result dans l'exemplepower sera recréée chaque fois que la fonction est appelée, et que ces incarnation séparées n'interfèrent pas l'une avec l'autre.

Cette “localité” des variables s'applique seulement aux paramètres et variables déclaréers avec le mot-clé var dans le corps de la fonction. Les variables déclarées en dehors de toute fonction sont dites globales, car elles sont visibles tout le long du programme. Il est possible d'accéder à de telles variables de l'intérieur d'une fonction, tant que vous n'avez pas déclaré une variable locale avec le même nom.

Le code suivant illustre cela. Il définit et appelle deux fonctions qui chacunes assignent une valeur a la variable x. Le premier déclare la variable comme locale, et donc change uniquement cette variable locale. Le second ne déclare pas x localement, donc les références as x a l'intérieur de la fonction réfèrent à la variable globale x définie au début de l'exemple.

var x = "outside";

var f1 = function() {
  var x = "inside f1";
};
f1();
console.log(x);
// → outside

var f2 = function() {
  x = "inside f2";
};
f2();
console.log(x);
// → inside f2

Ce comportement évite les interférences accidentelles entre les fonctions. Si toutes les variables étaient partagées par la totalité du programme, il prendrait beaucoup d'effort de s'assurer qu'aucun nom n'est utilisé pour deux choses différentes. Et si vous réutilisiez un nom de variable, vous pourriez observer d'étranges effets due à du code non relié qui mettrait le bazar dans la valeur de votre variable. En traitant les variables locales à la fonction comme n'existant que dans cette fonction, le langage rend possible de lire et comprendre les fonctions comme de petits univers, sans avoir a lire l'intégralité du code pour les comprendre.

Nichage de portée (Nested scope)

JavaScript ne fait pas seulement la distinction entre variables globales et locales. Des fonctions peuvent être crées dans d'autres, produisant plusieurs degrés de localité.

Par exemple, cette fonction plutôt absurde a deux fonction à l'intérieur d'elle-même :

var landscape = function() {
  var result = "";
  var flat = function(size) {
    for (var count = 0; count < size; count++)
      result += "_";
  };
  var mountain = function(size) {
    result += "/";
    for (var count = 0; count < size; count++)
      result += "'";
    result += "\\";
  };

  flat(3);
  mountain(4);
  flat(6);
  mountain(1);
  flat(1);
  return result;
};

console.log(landscape());
// → ___/''''\______/'\_

Les fonctions flat et mountain peuvent “voir” la variable appelée result, étant donné qu'elles se situent dans la fonction qui la définit. Mais elles ne peuvent pas voir la fonction count de l'autre car elles se situent hors de leur portée respective. L'environnement hors de la fonction landscape ne voit aucune des variables.

En bref, chaque scope local peut aussi voir les scope locaux qui le contienne. L'ensemble de valeurs visibles dans une fonction est déterminé par la place de la fonction dans le programme. Toutes la variables de blocs autour d'une définition de fonction sont visibles par celle-ci — cela signifiant que celle étant aussi bien dans le corps de la fonction parent que celle au plus haut niveau du programme. Cette approche de la visibilité des variables est appelée portée lexicale.

Les personnes ayant l'expérience d'autres langage de programmation pourrait s'attendre à ce que tout bloc de code entre accolades produise un nouvel environnement local. Mais en JavaScript, les fonctions sont le seul élément qui créé une nouvelle portée. Vous êtes libres d'utiliser des blocs autonomes.

var something = 1;
{
  var something = 2;
  // Do stuff with variable something...
}
// Outside of the block again...

Mais le something à l'intérieur des blocs réfère a la même variable qu'en dehors du bloc. En réalité, même si l'usage de bloc comme cela est autorisé, ils ne sont utiles que pour grouper le corps d'une instruction if ou d'une boucle

Si vous trouvez cela bizarre, vous n'êtes pas seul. La prochaine version de JavaScript introduira le mot clé let, qui fonctionne comme var mais créé une variable qui est locale au bloc l'entourant, et non à la fonction qui l'entoure.

Fonctions comme valeurs

Les variables fonctions s'utilisent habituellement comme des noms correspondant à un programme spécifique. Des telles variables sont définies une fois et ne sont jamais changées. Cela rend aisé la confusion entre une fonction et son nom.

Mais les deux sont différents. Une fonction peut faire les mêmes choses qu'une autre valeur peut faire — vous pouvez l'utiliser dans une expression arbitraire, et pas juste l'appeler. Il est possible de stocker une valeur fonction a un nouvel endroit, de la passer comme argument a une fonction, et ainsi de suite. De manière similaire, une variable qui contient une fonction reste une variable normale, et peut se voir réassigner une valeur, comme cela :

var launchMissiles = function(value) {
  missileSystem.launch("now");
};
if (safeMode)
  launchMissiles = function(value) {/* do nothing */};

Dans le Chapitre 5, nous discuterons des merveilleuses choses qui peuvent être faites en passant des valeurs fonction à d'autres fonctions.

Declaration notation

Il existe une manière légèrement plus courte de dire “var square = function…”. Le mot cléfunction peut aussi être utilisé au début d'une instruction, comme dans le suivant :

function square(x) {
  return x * x;
}

This is a function declaration. The statement defines the variable square and points it at the given function. So far so good. There is one subtlety with this form of function definition, however.

console.log("The future says:", future());

function future() {
  return "We STILL have no flying cars.";
}

Ce code fonctionne, même si la fonction est définie sous le code qui l'utilise. Cela est car les fonctions ne font pas partie du flux de contrôle habituel de haut en bas. Elles sont conceptuellement ramenée en haut de leur portée et peuvent être utilisées par tout le code de cette portée. C'est quelque chose qui est parfois utile car il nous permet d'ordonner notre code de manière signifiante, sans se préoccuper d'avoir définit toutes les fonctions avant leur premier usage.

Que se passe-t-il lorsque vous placez une telle fonction dans un bloc conditionnel(if) ou dans une boucle ? Eh bien, ne faites pas ça. Différentes plate-forme JavaScript dans différents navigateurs ont traditionnellement fait les choses différement dans cette situation, et le dernier standard l'interdit. Si vous souhaitez que votre programme réagisse de manière constante, n'utilisez cette forme d'instruction de définition de fonction dans le bloc le plus extérieur de la fonction ou du programme.

function example() {
  function a() {} // Okay
  if (something) {
    function b() {} // Danger!
  }
}

La pile d'appel (call stack)

Il sera utile de regarder avec plus d'attention the way control flows through functions. Voici une programme simple qui effectue quelques appels de fonctions :

function greet(who) {
  console.log("Hello " + who);
}
greet("Harry");
console.log("Bye");

Le parcours de ce programme donne en gros ceci : L'appel a greetcauses control to jump to the start of that function (line 2). Il appelle console.log (une fonction fournie par le navigateur), which takes control, fait ce qu'il a a faire, puis retourne le control to line 2. Il atteind alors la fin de la fonction greet function, et retourne à l'endroit où elle avait été appelée, soit la ligne 4. La ligne après celle-ci appelle à nouveau console.log.

Nous pouvons illustrer le flux de contrôle schématiquement comme ceci :

top
   greet
        console.log
   greet
top
   console.log
top

Comme une fonction doit retourner à l'endroit où elle se trouvait, l'ordinateur doit se remémorer le contexte dans lequel elle a été appelée. Dans un cas, console.log doit revenir en arrière à la fonction greet function. Dans l'autre il doit revenir à la fin du programme.

L'endroit où l'ordinateur stocke le contexte est appelé la pile d'appel (call stack). Chaque fois que la fonction est appelée, le contexte actuel est placé en haut de cette “pile”. Lorsque la fonction est retournée, elle enlève ce contexte du haut de la pile et l'utilise pour continuer son exécution.

Stocker cette pile requiers de l'espace dans la mémoire de l'ordinateur. Lorsque la pile est trop grande, l'ordinateur plante avec un message comme “out of stack space” ou “too much recursion”. Le code suivant illustre cela en posant à 'lordinateur une question très difficile, qui cause un aller-retour infini entre deux fonction. Ou plutôt, il devrait être infini, si l'ordinateur avait une pile infinie. En l'état, nous allons nous retrouver hors d'espace pour la pile, on va “blow the stack”.

function chicken() {
  return egg();
}
function egg() {
  return chicken();
}
console.log(chicken() + " came first.");
// → ??

Arguments optionnels

Le code suivant est permis et s'exécute sans problème :

alert("Hello", "Good Evening", "How do you do?");

La fonction alert n'accepte officiellement qu'un seul argument. Mais lorsqu'on l'apelle ainsi, elle ne renvoie pas d'erreur. Elle ignore simplement les autres arguments et affiche “Hello”.

JavaScript est trs tolérante sur le nombre d'arguments passés a une fonction. Si vous passez trop d'arguments, les arguments excédentaires sont ignorés. Si vous en passez trop peu, les arguments manquants se verront assigner la valeur undefined.

Les inconvénients de cela est qu'il est possible — probable, même — que vous passiez accidentellement le mauvais nombre d'arguments, et que personne ne vous en dira rien.

L'avantage est que ce comportement peut-être utilisé pour avoir une fonction qui prend des arguments “optionels”. Par exemple, la version suivante de power peut être appelée aussi bien avec deux arguments ou avec un seul, dans chaque cas l'exposant est supposé être 2, et la fonction se comportera comme square.

function power(base, exponent) {
  if (exponent == undefined)
    exponent = 2;
  var result = 1;
  for (var count = 0; count < exponent; count++)
    result *= base;
  return result;
}

console.log(power(4));
// → 16
console.log(power(4, 3));
// → 64

Dans le prochain chapitre, nous verrons une manière permettant au corps de la fonction d'obtenir le nombre d'arguments ayant été passés. Cela est utile car il rend possible pour une fonction d'accepter n'importe quel type d'argument. Par exemple, console.log l'utilise — cela lui permet d'imprimer toutes les valeurs qu'on lui donne.

console.log("R", 2, "D", 2);
// → R 2 D 2

Fermetures (Closure)

La capacité de traiter les fonctions comme des valeurs, combinée au fait que les variables locales sont “re-crées” chaque fois qu'une fonction est appelée, amène une question intéressante. Qu'arrive-t-il a une variable locale quand la fonction qui l'appelle n'est plus active ?

Le code suivant montre un exemple de cela. Il définit une fonction, wrapValue, qui créé une variable locale. Il retourne alors une fonction qui accède et retourne cette variable locale.

function wrapValue(n) {
  var localVariable = n;
  return function() { return localVariable; };
}

var wrap1 = wrapValue(1);
var wrap2 = wrapValue(2);
console.log(wrap1());
// → 1
console.log(wrap2());
// → 2

Cela est autorisé et fonctionne comme on peut l'attendre — la variable peut toujours être accédée. En fait, de multiples instances de la variable peuvent exister en même temps, ce qui est une autre bonne illustration du concept qui veut que les variables locales soient réellement recréés pour chaque appel — différents appels ne peuvent empiéter sur les variables locales des autres.

Cette fonctionnalité — être capable de référencer une instance spécifique de variables locales dans une fonction englobante — est appelée fermeture. Une fonction qui “ferme autour” de variables locales est appelée une fermeture. Ce comportement ne permet pas seulement de s'éviter d'avoir à s'occuper de la durée de vie des variables, mais permet aussi quelques usages créatifs des valeurs fonction.

Avec un léger changement, on peut modifier le précédent exemple de manière à créer des fonctions qui multiplient par un nombre arbitraires.

function multiplier(factor) {
  return function(number) {
    return number * factor;
  };
}

var twice = multiplier(2);
console.log(twice(5));
// → 10

La variable explicite localVariable de l'exemple wrapValue n'est plus nécéssaire étant donné qu'un paramètre est lui-même une variable locale.

Pensez les programme comme cela demande de la pratique. Un bon modèle mental est de penser le mot-clé function comme quelque chose qui “gèle” le code dans son corps et qui l'emballe dans un paquet (la valeur fonction). Donc lorsque vous lisez return function(…) {…}, pensez-le comme si vous retourniez un lien vers un morceau de calcul, congelé pour un usage ultérieur.

Dans l'exemple, multiplier retourne un morceau de code stockés dans la variable twice. La dernière ligne appelle alors la valeur dans cette variable, causant l'activation du code gelé (return number * factor;). Il a toujours accès à la variable factor de l'appel multiplicateur l'ayant créé, et additionnellement à l'argument qui lui est passé lorsqu'il est “dégelé”, 5, au travers de son paramètre number.

Récursion

Il est parfaitement correct pour une fonction de s'appeler elle-même, tant que l'on fait attention à ne pas exploser la pile d'appels. Une fonction qui s'appelle elle-même est dite récursive. La récursion permet à certaines fonctions d'être écrites avec un style différent. Prenez, par exemple, cette implémentation alternative de power:

function power(base, exponent) {
  if (exponent == 0)
    return 1;
  else
    return base * power(base, exponent - 1);
}

console.log(power(2, 3));
// → 8

Cela est assez proche de la manière dont les mathématiciens définissent l'élévation à la puissance et décrit probablement ce concept d'une manière plus élégante que la variante utilisant des boucles. L'appel a la fonction s'appelle lui-même de multiples fois avec des argument différents pour achever la multiplication répétée.

Mais cette implémentation a une problème important : dans les implémentations de JavaScript habituelle, elle est environ 10 fois plus lente que la version utilisant des boucles. Utiliser une boucle est bien plus léger que d'appeler une fonction de multiples fois.

Le dilemne de l'élégance contre la rapidité d'exécution est intéressant. Vous pouvez le voir comme la continuité du débat entre “human-friendliness” et “machine-friendliness”. À peu près n'importe quel programme peut être rendu plus rapide en augmentant sa taille et son caractère alambiqué. Le programmeur doit décider ce qui est le plus approprié. Dans le cas de la fonction power précédente , la version inélégante (looping) est toujours plutôt simple et facile à lire. Ça ne fait pas tellement sens de la remplacer par la version récursive. Toutefois, de temps à autre, un programme compose avec des problèmes bien plus complexes et et laisser de coté l'efficacité de manière à rendre le programme plus compréhensible devient un choix intéressant.

La règle basique, qui a été répétée à de nombreux programmeurs et à laquelle j'adhère totalement, est de ne pas s'occuper d'efficacité sauf si vous être certains que le programme est trop lent. S'il l'est, trouvez quelles parties prennent le plus de temps, et commencez à troquer l'élégance contre de l'efficacité dans ces parties.

Bien sûr, cette règle ne veut pas dire que vous devez ignorer les performances dans leur ensemble. Dans de nombreux cas, comme pour la fonction power, peu de simplicité est gagnée de l'approche “élégante”. Et de temps à autre un programmeur expérimenté peut voir directement comment une approche simple ne sera jamais suffisamment claire.

La raison qui explique que je mette l'accent sur ce point est qu'un nombre surprenant de programmeurs débutants se focalisent fanatiquement sur l'efficactité, même dans les plus petits détails. Il en résulte un code plus long, plus compliqué, et parfois moins correct, qui prend davantage de temps à écrire et ne s'éxécute habituellement que marginalement plus rapidement.

Mais la récursion n'est pas toujours moins efficace que les boucles. Certains problèmes sont bien plus facile à résoudre avec la récursion qu'avec des boucles. La plupart de ces problèmes sont des problèmes requérants l'exploration ou le traitement de différentes “branches”, chacune pouvant se diviser en de nouvelles branches.

Considérez ce puzzle : en commençant par le nombre 1 et, de manière répétée, soit en ajoutant 5, soit en multipliant par 3, un nombre infini de nombres peuvent être produits. Comment écriveriez-vous une fonction qui, pour un nombre donné, trouve une séquence des ces additions et multiplications qui produise ledit nombre ? Par exemple le nombre 13 peut être atteint en multipliant par 3 et en ajoutant 5 deux fois, alors que le nombre 15 ne peut être atteint du tout.

Voici une solution récursive :

function findSolution(target) {
  function find(start, history) {
    if (start == target)
      return history;
    else if (start > target)
      return null;
    else
      return find(start + 5, "(" + history + " + 5)") ||
             find(start * 3, "(" + history + " * 3)");
  }
  return find(1, "1");
}

console.log(findSolution(24));
// → (((1 * 3) + 5) * 3)

Notez que ce programme ne trouvera pas nécessairement la séquence d'opération la plus courte. Il se satisfait de toute séquence trouvée.

Je n'attend pas nécéssairement que vous compreniez comment cela fonctionne immédiatement. Mais attachons-nous y, car il s'agit d'un bel exercice de pensée récursive.

La fonction intérieur find opère la récursion. Elle prend deux arguments — le nombre actuel et une chaîne de caractère qui enregistre comment nous somme arrivé à ce nombre — et retourne soit une chaîne de caractère qui montre comment atteindre la cible, soit null.

Pour cela, la fonction exécute une des trois actions. Si le nombre est le nombre cible, l'historique actuel est celui qui permet de l'atteindre, il est donc simplement renvoyé. Si le nombre est supérieur à la cible, il est inutile de continuer à explorer cette branche puisque des ajouts ou multiplications consécutives ne feront que rendre le nombre plus grand. Et finalement, si nous sommes toujours en dessous de la cible, la fonction essaie les autres chemins possibles commençant par le numéro actuel, en s'appelant elle-même, une pour chacun des prochaines étapes possibles. Si le premier appel renvoie quelque chose qui n'est pas null, ce quelque chose est renvoyé. Sinon, le second appel est renvoyé — cela qu'il produise une chapine de caractère ou une valeur null.

Pour mieux comprendre comment cette fonction produit les effets que nous recherchons, jetons un oeil aux appels à find qui sont fait lorsque l'on cherche une solution pour le nombre 13.

find(1, "1")
  find(6, "(1 + 5)")
    find(11, "((1 + 5) + 5)")
      find(16, "(((1 + 5) + 5) + 5)")
        too big
      find(33, "(((1 + 5) + 5) * 3)")
        too big
    find(18, "((1 + 5) * 3)")
      too big
  find(3, "(1 * 3)")
    find(8, "((1 * 3) + 5)")
      find(13, "(((1 * 3) + 5) + 5)")
        found!

L'indentation suggère la profondeur de la pile d'appel. La première fois quefind est appelée, elle s'appelle elle-même deux fois pour explorer les solutions commençant par (1 + 5) et (1 * 3). Le premier appel essaye de trouver une solution qui commence par(1 + 5) et, utilisant la récursion, explore toutes les solutions qui renvoie un nombre inférieur ou égal au nombre cible. S'il ne trouve pas de solution égal à la cible, il renvoie null à l'appel d'origine. Ici l'opérateur || fait explorer à l'appel la branche (1 * 3. Cette recherche a davantage de chance car, a son premier appel récursif, lui-même au travers d'un autre appel récursif, atteint le nombre cible, 13. Ce appel récursif au plus bas niveau renvoie une chaîne de caractère, et chaque opérateur || operators dans les appels intermédiaires passe cette chaîne, renvoyant ultimemant notre solution.

Développer des fonctions (Growing functions)

Il y a deux manières plus ou moins naturelles d'introduire des fonctions dans les programmes. La première est lorsque vous vous trouvez à réécrire un code identique ou très similaire de multiples fois. Nous voulons éviter cela, étant donné que plus de code signifie plus d'espace pour des erreurs et plus de lecture pour les personnes essayant de comprendre le problème. On prend alors la fonctionnalité répétée, on lui trouve un bon nom, et on la met dans une fonction.

La seconde est lorsque vous avez besoin d'une fonctionnalité que vous n'avez pas encore écrit et qu'il semble que cela mérite sa propre fonction. Vous commencez par nommer la fonction, puis vous écrivez son corps. Vous pouvez même commencer à écrire du code qui utilise la fonction avant de l'écrire elle-même.

La difficulté pour trouver le nom de la fonction est un bon indicateur d'a quel point est clair le concept de ce que vous essayez d'encapsuler. Regardons un exemple. On souhaite écrire un programme qui imprime deux nombres, le nombre de vaches et de poulets d'une ferme, suivis des mots Cows et Chicken, et avec des zéros devant le nombre de manière à ce que celui-ci prenne toujours 3 chiffres.

007 Cows
011 Chickens

Cela demande clairement une fonction avec deux arguments. Commençons à coder.

function printFarmInventory(cows, chickens) {
  var cowString = String(cows);
  while (cowString.length < 3)
    cowString = "0" + cowString;
  console.log(cowString + " Cows");
  var chickenString = String(chickens);
  while (chickenString.length < 3)
    chickenString = "0" + chickenString;
  console.log(chickenString + " Chickens");
}
printFarmInventory(7, 11);

Ajouter .length après une chaîne de caractères nous donne la longueur de celle-ci. Donc, la boucle while continue d'ajouter des zéros devant la chaîne tant que sa longueur n'est pas au moins égale à trois.

Mission accomplie ! Mais alors que nous allions envoyer le code au fermier (avec une jolie facture, bien évidemment), l nous appelle et nous dit qu'il commence aussi à élever des cochons, est-ce qu'on pourrait ajouter cela au logiciel.

Bien sûr que l'on peut. Mais alors que nous étions en train de copier et coller ces quatre lignes une fois de plus, on s'arrête et l'on se dit qu'il doit y avoir une meilleure manière. Voici une première tentative :

function printZeroPaddedWithLabel(number, label) {
  var numberString = String(number);
  while (numberString.length < 3)
    numberString = "0" + numberString;
  console.log(numberString + " " + label);
}

function printFarmInventory(cows, chickens, pigs) {
  printZeroPaddedWithLabel(cows, "Cows");
  printZeroPaddedWithLabel(chickens, "Chickens");
  printZeroPaddedWithLabel(pigs, "Pigs");
}

printFarmInventory(7, 11, 3);

Ça marche ! Mais avec le nom, printZeroPaddedWithLabel, est un peu bizarre. il combine trois choses — imprimer, ajouter des zéros, et ajouter un label — dans une seule fonction.

Plutôt que de rassembler les parties répétées de notre programme, essayons de trouver un seul concept.

function zeroPad(number, width) {
  var string = String(number);
  while (string.length < width)
    string = "0" + string;
  return string;
}

function printFarmInventory(cows, chickens, pigs) {
  console.log(zeroPad(cows, 3) + " Cows");
  console.log(zeroPad(chickens, 3) + " Chickens");
  console.log(zeroPad(pigs, 3) + " Pigs");
}

printFarmInventory(7, 16, 3);

Une fonction avec un nom simple et évident comme zeroPad rend plus facile la lecture pour quelqu'un qui lirait le code pour comprendre ce qu'il fait. And it is useful in more situations than just this specific program. For example, you could use it to help print nicely aligned tables of numbers.

À quelle point notre fonction devrait-elle être intelligente et versatile ? Nous pourrions écrire bien des choses, d'une fonction simple qui 'pads' un nombre afin qu'il fasse trois caractères de long, à une fonction générale et compliquée de formatage de nombres qui supporte les fractions, les nombres négatifs, les alignements de points, le padding avec difféents caratères, etc.

Une règle utile est de ne pas ajouter trop d'intelligence, sauf si vous êtes absolument sûr d'en avoir besoin. Il peut être tentant d'écrire des “frameworks” générique pour chaque petite fonctionnalité dont vous pourriez avoir besoin. Résister à cela. Vous n'accomplirez rien de la sorte, et vous retrouveriez à écrire beaucoup de code que vous n'utiliserez jamais. Fonctions et effets de bord

Les fonctions peuvent être en gros divisées entre ceux appelées pour leurs effets de bord et celles appelées pour la valeur qu'elle retournent. (Quoiqu'il soit définitivement possible d'avoir une fonction qui ait des effets de bord et qui retourne une valeur.)

Le premier exemple de fonction utile dans l'exemple de la ferme, printZeroPaddedWithLabel, est appelée pour son effet de bord : il imprime une ligne. La seconde version, zeroPad, est appelée pour sa valeur de retour. Ce n'est pas une coïncidence si la seconde est utile dans davantage de situations que la première. Les fonctions qui créent des valeurs sont plus faciles à combiner de façons nouvelles que celle qui ont directement des effets de bord.

Une pure fonction est un genre spécifique de fonction produisant une valeur qui n'a aucun effet de bord mais aussi ne compte pas sur les effets de bord d'autre code — par exemple, il ne lit pas de variables globales qui sont occasionnellement changées par un autre code. Une pure fonction a la plaisante propriété qui, lorsque appelée avec les même argument, donne toujours la même valeur (et ne change rien d'autre). Cela rend facile la réflexion sur celle-ci.Un appel à une tel fonction peut mentalement être remplacée par son résultat. Lorsque vous n'êtes pas certain qu'une fonction pure marche correctement, vous pouvez la tester simplement en l'appelant, et savoir que si cela fonctionne dans ce contexte, cela marchera dans n'importe lequel. Les fonctions non-pure peuvent retourner différentes valeurs sur la base d'une multitude de facteurs et ont des effets de bord qui peuvent être compliqué à tester et à penser. Toutefois, pas besoin de se sentir mal en écrivant des fonctions qui ne sont pas pures ou de mener une guerre sainte pour les évacuer de votre code. Les effets de bords sont souvent utiles. Il n'y a aucun moyen d'écrire une version pure fonction de console.log par, exemple, et console.log est très certainement utile. Certaines opérations sont plus facile à exprimer de manière efficace en utilisant des effets de bord, la rapidité de calcul peut donc être une raison d'éviter la pureté.

Sommaire

Ce chapitre vous a expliqué comment écrire vos propre fonctions. Le mot-clé function, lorsqu'utilisé comme comme une expression, peut créer une valeur fonction. Lorsqu'utilisé comme une instruction, elle peut être utilisée pour déclarer une variable et lui donner une fonction comme valeur.

// Create a function value f
var f = function(a) {
  console.log(a + 2);
};

// Declare g to be a function
function g(a, b) {
  return a * b * 3.5;
}

Un aspect clé dans la compréhension des fonctions est la compréhension de la porté locale. Les paramètres et variables déclarés à l'intérieur de la fonction sont locales à la fonction, re-créés chaque fois que la fonction est appelé, et non visibles de l'extérieur. Les fonctions déclarées à l'intérieur d'une autre fonction ont accès à la portée externe de cette fonction. Séparer les tâches de votre programme en différentes fonctions est utile. Vous navez pas à vous répéter autant, et les fonctions peuvent rendre un programme plus lisible en regroupant du code dans des blocs conceptuels, de la même manière que les chapitres et sections aident à l'organisation d'un texte.

Exercices

Minimum

Le chapitre précédent introduisait la fonction standard Math.min qui retourne son plus petit argument. Nous pouvons faire cela nous-même maintenant. Écrivez une fonction min qui prend deux argument et renvoi le plus petit.

// Your code here.

console.log(min(0, 10));
// → 0
console.log(min(0, -10));
// → -10

Récursion

Nous avons vu que % (l'opérateur reste) peut être utiliser pour tester si un nombre est pair ou impair en utilisant % 2 pour tester s'il est divisible par deux. Voici une autre manière de définir si un nombre positif est pair ou impair :

  • Zéro est pair.
  • Un est impair.
  • Pour tout autre nombre N, sa parité est la même que N - 2.

Définissez une fonction récursive isEven correspondant à la description. Cette fonction doit accepter un number comme paramètre et retourner un booléan.

Testez le sur sur 50 et 75. Voyez comme il se comporte abev -1. Pourquoi ? Pouvez-vous penser à une manière de corriger cela ?

// Your code here.

console.log(isEven(50));
// → true
console.log(isEven(75));
// → false
console.log(isEven(-1));
// → ??

Compter les haricots

Vous pouvez obtenir le n-ième caractère, ou lettre, d'une chaîne de caractère en écrivant “string”.charAt(N), de manière similaire à lorsque vous souhaitez obtenir sa longueur avec “s”.length. La valeur retournée sera une chaîne de caractère contenant un seul caractère (par exemple “b”). Le premier caractère a pour position 0, ce qui fait que l'on trouve le dernier caractère à la position string.length - 1. En d'autres mots, une chaîne de caractères contenant deux caractères a une longueur de 2, et ses caractères ont les position 0 et 1.

Écrivez une fonction countBs qui prend une chaîne de caractère et retourne un nombre qui indique combien de “B” majuscules sont présent dans la chaîne.

Ensuite, créez une fonction countChar qui se comporte comme countBs, à cela près qu'elle prend un second argument qui indique le caractère à compter (plutôt que de compter uniquement les “B” majuscules). Réécrivez countBs pour utiliser cette nouvelle fonction.

console.log(countBs("BBC"));
// → 2
console.log(countChar("kakkerlak", "k"));
// → 4
javascript/eloquent_javascript/functions.txt · Last modified: 2016/10/01 20:05 by leo