Comme nous le savons déjà , une fonction en JavaScript est une valeur.
Chaque valeur en JavaScript a un type. De quel type est une fonction ?
Pour JavaScript, les fonctions sont des objets.
Un bon moyen dâimaginer des fonctions est en tant que des âobjets dâactionâ quâon peut appeler. Nous pouvons non seulement les appeler, mais aussi les traiter comme des objets : ajouter/supprimer des propriĂ©tĂ©s, passer par rĂ©fĂ©rence, etc.
La propriĂ©tĂ© ânameâ
Les objets Fonction contiennent des propriétés utilisables.
Par exemple, le nom dâune fonction est accessible en tant que propriĂ©tĂ© ânameâ :
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
Ce qui est drĂŽle, câest que la logique dâattribution de noms est intelligente. Elle attribue Ă©galement le nom correct Ă une fonction mĂȘme si elle est créée sans, puis immĂ©diatement attribuĂ© :
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (il y a un nom !)
Cela fonctionne aussi si lâaffectation est faite avec une valeur par dĂ©faut :
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (ça marche !)
}
f();
Dans la spĂ©cification, cette fonctionnalitĂ© est appelĂ©e âcontextual nameâ. Si la fonction nâen fournit pas, elle est dĂ©terminĂ©e Ă partir du contexte lors de lâaffectation.
Les mĂ©thodes dâobjet ont aussi des noms :
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Cependant câest pas magique. Il y a des cas oĂč il nây a aucun moyen de trouver le bon nom. Dans ce cas, la propriĂ©tĂ© name est vide, comme ci-dessous :
// fonction créée dans un tableau
let arr = [function() {}];
alert( arr[0].name ); // <chaĂźne de caractĂšres vide>
// le moteur n'a aucun moyen de définir le bon nom. Donc, il n'y en a pas
Par contre, en pratique la plupart des fonctions ont un nom.
La propriĂ©tĂ© âlengthâ
Il existe une autre propriĂ©tĂ© native, âlengthâ, qui renvoie le nombre de paramĂštres de la fonction, par exemple :
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
Nous pouvons voir que les paramÚtres du reste ne sont pas comptés.
La propriĂ©tĂ© length est parfois utilisĂ©e pour la rĂ©flĂ©xion (introspection en anglais) dans des fonctions qui opĂšrent sur dâautres fonctions.
Par exemple, dans le code ci-dessous, la fonction ask accepte une question Ă poser et un nombre arbitraire de fonctions handler (gestionnaires) Ă appeler.
Une fois quâun utilisateur a fourni sa rĂ©ponse, la fonction appelle les gestionnaires. Nous pouvons transmettre deux types de gestionnaires :
- Une fonction sans argument, qui nâest appelĂ©e que lorsque lâutilisateur donne une rĂ©ponse positive.
- Une fonction avec des arguments, appelée dans les deux cas et renvoyant une réponse.
Pour appeler handler correctement, nous examinons la propriété handler.length.
LâidĂ©e est que nous avons une syntaxe de gestionnaire simple, sans argument, pour les cas positifs (variante la plus frĂ©quente), mais que nous pouvons Ă©galement prendre en charge les gestionnaires universels :
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// pour une réponse positive, les deux gestionnaires sont appelés
// pour une réponse négative, seulement le second
ask("Question?", () => alert('You said yes'), result => alert(result));
Ceci est un cas particulier de ce quâon appelle le polymorphism â le traitement des arguments diffĂ©remment selon leur type ou, dans notre cas, en fonction de la length. Cette approche est utilisĂ©e dans les bibliothĂšques JavaScript.
Propriétés personnalisées
Nous pouvons également ajouter nos propres propriétés.
Nous ajoutons ici la propriĂ©tĂ© counter pour suivre le nombre total dâappels :
function sayHi() {
alert("Hi");
// comptons combien de fois nous executons
sayHi.counter++;
}
sayHi.counter = 0; // valeur initiale
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Appelée 2 fois
Une propriĂ©tĂ© affectĂ©e Ă une fonction comme sayHi.counter = 0 ne dĂ©finit pas une variable locale counter Ă lâintĂ©rieur de celle-ci. En dâautres termes, une propriĂ©tĂ© counter et une variable let counter sont deux choses indĂ©pendantes.
On peut traiter une fonction comme un objet, y stocker des propriĂ©tĂ©s, mais cela nâa aucun effet sur son exĂ©cution. Les variables ne sont pas des propriĂ©tĂ©s de fonction et inversement. Ce sont des mondes parallĂšles.
Les propriĂ©tĂ©s de fonction peuvent parfois remplacer les fermetures. Par exemple, nous pouvons réécrire lâexemple de fonction de compteur du chapitre Variable scope, closure pour utiliser une propriĂ©tĂ© de fonction :
function makeCounter() {
// au lieu de :
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
Le count est maintenant stocké dans la fonction directement, pas dans son environnement lexical externe.
Est-ce meilleur ou pire que dâutiliser une fermeture ?
La principale diffĂ©rence est que si la valeur de count rĂ©side dans une variable externe, le code externe ne peut pas y accĂ©der. Seules les fonctions imbriquĂ©es peuvent le modifier. Et si câest liĂ© Ă une fonction, une telle chose est possible :
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
Le choix dépend donc de nos objectifs.
Fonction Expression Nommée (NFE)
Fonction Expression NommĂ©e, ou NFE (âNamed Function Expressionâ en anglais), est un terme pour les fonctions expressions qui ont un nom.
Par exemple, prenons une fonction expression ordinaire :
let sayHi = function(who) {
alert(`Hello, ${who}`);
};
Et ajoutons un nom Ă cela :
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
Avons-nous réalisé quelque chose ici ? Quel est le but de ce nom supplémentaire "func" ?
Notons dâabord que nous avons toujours une expression de fonction. Lâajout du nom "func" aprĂšs function nâen a pas fait une dĂ©claration de fonction, car il est toujours créé dans le cadre dâune expression dâaffectation.
Lâajout dâun tel nom nâa Ă©galement rien cassĂ©.
La fonction est toujours disponible sous la forme sayHi() :
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
Il y a deux particularités à propos du nom func, voici les raisons :
- Il permet à la fonction de se référencer en interne.
- Il nâest pas visible en dehors de la fonction.
Par exemple, la fonction sayHi ci-dessous sâappelle Ă nouveau avec "Guest" si aucun who est fourni :
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // utilise func pour se rappeler
}
};
sayHi(); // Hello, Guest
// Mais ceci ne marchera pas :
func(); // Error, func is not defined (pas visible à l'extérieur de la fonction)
Pourquoi utilisons-nous func ? Peut-ĂȘtre juste utiliser sayHi pour lâappel imbriquĂ© ?
En fait, dans la plupart des cas, nous pouvons :
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
Le problÚme avec ce code est que sayHi peut changer dans le code externe. Si la fonction est assignée à une autre variable, le code commencera à donner des erreurs :
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Error: sayHi is not a function
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Error, l'appel sayHi imbriqué ne fonctionne plus !
Cela se produit parce que la fonction tire sayHi de son environnement lexical externe. Il nây a pas de sayHi local, donc la variable externe est utilisĂ©e. Et au moment de lâappel, ce sayHi extĂ©rieur est null.
Le nom optionnel que nous pouvons mettre dans lâexpression de fonction est destinĂ© Ă rĂ©soudre exactement ce type de problĂšmes.
Utilisons-le pour corriger notre code :
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Maintenant tout va bien
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (l'appel imbriqué fonctionne)
Maintenant cela fonctionne, car le nom 'func' est local Ă la fonction. Il nâest pas pris de lâextĂ©rieur (et non visible lĂ -bas). La spĂ©cification garantit quâelle fera toujours rĂ©fĂ©rence Ă la fonction actuelle.
Le code externe a toujours sa variable sayHi ou welcome. Et func est un ânom de fonction interneâ, la façon dont la fonction peut sâappeler de maniĂšre fiable.
La fonctionnalitĂ© ânom interneâ dĂ©crite ici nâest disponible que pour les expressions de fonction, pas pour les dĂ©clarations de fonction. Pour les dĂ©clarations de fonctions, il nây a aucune possibilitĂ© de syntaxe dâajouter un nom âinterneâ supplĂ©mentaire.
Parfois, lorsque nous avons besoin dâun nom interne fiable, câest la raison pour laquelle nous réécrivons une dĂ©claration de fonction en tant quâexpression de fonction nommĂ©e.
Résumé
Les fonctions sont des objets.
Ici nous avons couvert leurs propriétés :
nameâ le nom de la fonction. Habituellement tirĂ© de la dĂ©finition de la fonction, mais sâil nâen existe pas, JavaScript essaie de le deviner Ă partir du contexte (par exemple, une affectation).lengthâ le nombre dâarguments dans la dĂ©finition de la fonction. Les paramĂštres du reste ne sont pas comptĂ©s.
Si la fonction est dĂ©clarĂ©e en tant quâexpression de fonction (et non dans le flux du code principal) et quâelle porte un nom, elle est appelĂ©e expression de fonction nommĂ©e. Le nom peut ĂȘtre utilisĂ© Ă lâintĂ©rieur pour se rĂ©fĂ©rencer, pour des appels rĂ©cursifs ou autres.
Les fonctions peuvent également comporter des propriétés supplémentaires. De nombreuses bibliothÚques JavaScript bien connues font un grand usage de cette fonctionnalité.
Elles crĂ©ent une fonction âprincipaleâ et y attachent de nombreuses autres fonctions âdâassistanceâ. Par exemple, la bibliothĂšque jQuery crĂ©e une fonction nommĂ©e $. La bibliothĂšque lodash crĂ©e une fonction _ et ajoute ensuite _.clone, _.keyBy et dâautres propriĂ©tĂ©s (voir la doc lorsque vous souhaitez en savoir plus Ă leur sujet). En fait, elles le font pour rĂ©duire leur pollution de lâespace global, de sorte quâune seule bibliothĂšque ne donne quâune seule variable globale. Cela rĂ©duit la possibilitĂ© de conflits de noms.
Ainsi, une fonction peut faire un travail utile par elle-mĂȘme et aussi porter un tas dâautres fonctionnalitĂ©s dans les propriĂ©tĂ©s.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)