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 ?
En JavaScript, les fonctions sont des objets.
Une bonne façon d’imaginer les fonctions est de les considérer comme des « objets d’action » appelables. On peut non seulement les appeler, mais aussi les traiter comme des objets : ajouter/supprimer des propriétés, passer par référence etc.
Les objets fonction contiennent certaines propriétés utilisables.
Par exemple, le nom d'une fonction est accessible via la propriété « name » :
fonction direSalut() { alert("Bonjour"); } alert(sayHi.name); // dis bonjour
Ce qui est plutôt drôle, c'est que la logique d'attribution des noms est intelligente. Il attribue également le nom correct à une fonction même si elle est créée sans en avoir un, puis immédiatement attribué :
disonsSalut = fonction() { alert("Bonjour"); } ; alert(sayHi.name); // disBonjour (il y a un nom !)
Cela fonctionne également si l'affectation se fait via une valeur par défaut :
fonction f(sayHi = fonction() {}) { alert(sayHi.name); // disBonjour (fonctionne !) } f();
Dans la spécification, cette fonctionnalité est appelée « nom contextuel ». Si la fonction n'en fournit pas, alors dans une affectation, elle est déterminée à partir du contexte.
Les méthodes objets ont également des noms :
laissez l'utilisateur = { disBonjour() { //... }, sayBye : fonction() { //... } } alerte (utilisateur.sayHi.name); // dis bonjour alerte (utilisateur.sayBye.name); // dire au revoir
Mais il n’y a pas de magie. Il y a des cas où il n'est pas possible de trouver le bon nom. Dans ce cas, la propriété name est vide, comme ici :
// fonction créée dans le tableau laissez arr = [function() {}]; alerte( arr[0].name ); // <chaîne vide> // le moteur n'a aucun moyen de définir le bon nom, donc il n'y en a pas
Toutefois, dans la pratique, la plupart des fonctions ont un nom.
Il existe une autre propriété intégrée « longueur » qui renvoie le nombre de paramètres de fonction, par exemple :
fonction f1(a) {} fonction f2(a, b) {} fonction plusieurs (a, b, ...plus) {} alerte(f1.length); // 1 alerte(f2.length); // 2 alert(beaucoup.longueur); // 2
Ici, nous pouvons voir que les paramètres de repos ne sont pas comptés.
La propriété length
est parfois utilisée pour l'introspection 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
à appeler.
Une fois qu'un utilisateur fournit sa réponse, la fonction appelle les gestionnaires. Nous pouvons passer par 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, qui est appelée dans les deux cas et renvoie une réponse.
Pour appeler handler
de la bonne manière, nous examinons la propriété handler.length
.
L'idée est que nous disposons d'une syntaxe de gestion simple et sans arguments pour les cas positifs (variante la plus fréquente), mais que nous sommes également capables de prendre en charge les gestionnaires universels :
fonction demander (question, ... gestionnaires) { let isYes = confirmer (question); pour (laisser le gestionnaire des gestionnaires) { if (handler.length == 0) { if (isYes) gestionnaire(); } autre { gestionnaire (estOui); } } } // pour une réponse positive, les deux gestionnaires sont appelés // pour une réponse négative, seulement la deuxième request("Question ?", () => alert('Vous avez dit oui'), result => alert(result));
Il s'agit d'un cas particulier de ce qu'on appelle le polymorphisme – traiter les arguments différemment selon leur type ou, dans notre cas, selon la length
. L'idée a une utilité dans les bibliothèques JavaScript.
Nous pouvons également ajouter nos propres propriétés.
Ici, nous ajoutons la propriété counter
pour suivre le nombre total d'appels :
fonction direSalut() { alert("Bonjour"); // comptons combien de fois nous courons sayHi.counter++; } sayHi.counter = 0; // valeur initiale disBonjour(); // Salut disBonjour(); // Salut alert( `Appelé ${sayHi.counter} fois` ); // Appelé 2 fois
Une propriété n'est pas une variable
Une propriété affectée à une fonction comme sayHi.counter = 0
ne définit pas de counter
de variable locale à l'intérieur. En d’autres termes, un counter
de propriétés et un let counter
sont deux éléments sans rapport.
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 vice versa. Ce ne sont que 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 compteur du chapitre Portée des variables, fermeture pour utiliser une propriété de fonction :
fonction makeCounter() { // au lieu de: // laissez compter = 0 fonction compteur() { retourner compteur.count++ ; } ; compteur.count = 0; comptoir de retour ; } laissez compteur = makeCounter(); alerte( compteur() ); // 0 alerte( compteur() ); // 1
Le count
est désormais stocké directement dans la fonction, et non dans son environnement lexical externe.
Est-ce mieux 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, alors une telle chose est possible :
fonction makeCounter() { fonction compteur() { retourner compteur.count++ ; } ; compteur.count = 0; comptoir de retour ; } laissez compteur = makeCounter(); compteur.count = 10 ; alerte( compteur() ); // 10
Le choix de la mise en œuvre dépend donc de nos objectifs.
L'expression de fonction nommée, ou NFE, est un terme désignant les expressions de fonction qui ont un nom.
Par exemple, prenons une expression de fonction ordinaire :
disonsSalut = fonction (qui) { alert(`Bonjour, ${who}`); } ;
Et ajoutez-y un nom :
laissez direSalut = function func(who) { alert(`Bonjour, ${who}`); } ;
Avons-nous accompli quelque chose ici ? Quel est le but de ce nom "func"
supplémentaire ?
Notons d’abord que nous avons toujours une expression de fonction. L'ajout du nom "func"
après function
n'en fait pas une déclaration de fonction, car elle est toujours créée dans le cadre d'une expression d'affectation.
L’ajout d’un tel nom n’a rien cassé non plus.
La fonction est toujours disponible sous la forme sayHi()
:
laissez direSalut = function func(who) { alert(`Bonjour, ${who}`); } ; sayBonjour("Jean"); // Bonjour, Jean
Il y a deux choses spéciales à propos du nom func
, qui en sont 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
n'est fourni :
laissez direSalut = function func(who) { si (qui) { alert(`Bonjour, ${who}`); } autre { func("Invité"); // utilise func pour se rappeler } } ; disBonjour(); // Bonjour, Invité // Mais ça ne marchera pas : fonction(); // Erreur, la fonction n'est pas définie (non visible en dehors de la fonction)
Pourquoi utilisons-nous func
? Peut-être simplement utiliser sayHi
pour l'appel imbriqué ?
En fait, dans la plupart des cas, nous pouvons :
disonsSalut = fonction (qui) { si (qui) { alert(`Bonjour, ${who}`); } autre { sayHi("Invité"); } } ;
Le problème avec ce code est que sayHi
peut changer dans le code externe. Si la fonction est affectée à une autre variable à la place, le code commencera à donner des erreurs :
disonsSalut = fonction (qui) { si (qui) { alert(`Bonjour, ${who}`); } autre { sayHi("Invité"); // Erreur : sayHi n'est pas une fonction } } ; laissez bienvenue = ditesSalut ; sayHi = null; accueillir(); // Erreur, l'appel imbriqué sayHi ne fonctionne plus !
Cela se produit parce que la fonction extrait sayHi
de son environnement lexical externe. Il n'y a pas sayHi
local, donc la variable externe est utilisée. Et au moment de l'appel, ce sayHi
externe est null
.
Le nom facultatif que nous pouvons mettre dans l’expression de fonction est destiné à résoudre exactement ce genre de problèmes.
Utilisons-le pour corriger notre code :
disonsSalut = function func(who) { si (qui) { alert(`Bonjour, ${who}`); } autre { func("Invité"); // Maintenant tout va bien } } ; laissez bienvenue = ditesSalut ; sayHi = null; accueillir(); // Bonjour, Invité (l'appel imbriqué fonctionne)
Maintenant, cela fonctionne, car le nom "func"
est une fonction locale. Il n’est pas pris de l’extérieur (et n’y est pas visible). 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 », permettant à la fonction de s'appeler de manière fiable.
Il n'y a rien de tel pour la déclaration de fonction
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 fonction, il n'existe pas de syntaxe pour ajouter un nom « interne ».
Parfois, lorsque nous avons besoin d'un nom interne fiable, c'est la raison pour laquelle il faut réécrire une déclaration de fonction sous la forme d'une expression de fonction nommée.
Les fonctions sont des objets.
Ici, nous avons couvert leurs propriétés :
name
– le nom de la fonction. Généralement tiré de la définition de la fonction, mais s'il n'y en a 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 de repos ne sont pas comptés.
Si la fonction est déclarée comme expression de fonction (pas dans le flux de code principal) et qu'elle porte le nom, elle est alors 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.
De plus, les fonctions peuvent comporter des propriétés supplémentaires. De nombreuses bibliothèques JavaScript bien connues font grand usage de cette fonctionnalité.
Ils créent une fonction « principale » et y attachent de nombreuses autres fonctions « auxiliaires ». Par exemple, la bibliothèque jQuery crée une fonction nommée $
. La bibliothèque lodash crée une fonction _
, puis y ajoute _.clone
, _.keyBy
et d'autres propriétés (voir la documentation lorsque vous souhaitez en savoir plus à leur sujet). En fait, ils 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 effectuer un travail utile par elle-même et également comporter de nombreuses autres fonctionnalités dans les propriétés.
importance : 5
Modifiez le code de makeCounter()
pour que le compteur puisse également diminuer et définir le nombre :
counter()
devrait renvoyer le numéro suivant (comme avant).
counter.set(value)
devrait définir le compteur sur value
.
counter.decrease()
devrait diminuer le compteur de 1.
Consultez le code du bac à sable pour un exemple d'utilisation complet.
PS Vous pouvez utiliser soit une fermeture, soit la propriété function pour conserver le décompte actuel. Ou écrivez les deux variantes.
Ouvrez un bac à sable avec des tests.
La solution utilise count
dans la variable locale, mais les méthodes d'addition sont écrites directement dans le counter
. Ils partagent le même environnement lexical externe et peuvent également accéder au count
actuel.
fonction makeCounter() { laissez compter = 0 ; fonction compteur() { renvoie le nombre++ ; } counter.set = valeur => count = valeur ; counter.decrease = () => count--; comptoir de retour ; }
Ouvrez la solution avec des tests dans un bac à sable.
importance : 2
Écrivez sum
de fonction qui fonctionnerait comme ceci :
somme(1)(2) == 3; // 1 + 2 somme(1)(2)(3) == 6; // 1 + 2 + 3 somme(5)(-1)(2) == 6 somme(6)(-1)(-2)(-3) == 0 somme(0)(1)(2)(3)(4)(5) == 15
Astuce PS : vous devrez peut-être configurer un objet personnalisé en conversion primitive pour votre fonction.
Ouvrez un bac à sable avec des tests.
Pour que tout fonctionne de toute façon , le résultat de sum
doit être une fonction.
Cette fonction doit garder en mémoire la valeur actuelle entre les appels.
Selon la tâche, la fonction doit devenir le nombre lorsqu'elle est utilisée dans ==
. Les fonctions sont des objets, donc la conversion se produit comme décrit dans le chapitre Conversion d'objet en primitive, et nous pouvons fournir notre propre méthode qui renvoie le nombre.
Maintenant le code :
fonction somme(a) { soit currentSum = a; fonction f(b) { Somme actuelle += b; retourner f; } f.toString = fonction() { renvoie la Somme actuelle ; } ; retourner f; } alerte( somme(1)(2) ); // 3 alerte( somme(5)(-1)(2) ); // 6 alerte( somme(6)(-1)(-2)(-3) ); // 0 alerte( somme(0)(1)(2)(3)(4)(5) ); // 15
Veuillez noter que la fonction sum
ne fonctionne en réalité qu'une seule fois. Il renvoie la fonction f
.
Ensuite, à chaque appel ultérieur, f
ajoute son paramètre à la somme currentSum
et se renvoie lui-même.
Il n'y a pas de récursion dans la dernière ligne de f
.
Voici à quoi ressemble la récursivité :
fonction f(b) { Somme actuelle += b; retourner f(); // <-- appel récursif }
Et dans notre cas, on renvoie simplement la fonction, sans l'appeler :
fonction f(b) { Somme actuelle += b; retourner f; // <-- ne s'appelle pas, se renvoie }
Ce f
sera utilisé lors du prochain appel, se retournera à nouveau, autant de fois que nécessaire. Ensuite, lorsqu'il est utilisé comme nombre ou chaîne, le toString
renvoie le currentSum
. Nous pourrions également utiliser Symbol.toPrimitive
ou valueOf
ici pour la conversion.
Ouvrez la solution avec des tests dans un bac à sable.