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â¦)