Comme nous le savons dj, 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 dimaginer des fonctions est en tant que des objets daction quon peut appeler. Nous pouvons non seulement les appeler, mais aussi les traiter comme des objets : ajouter/supprimer des proprits, passer par rfrence, etc.
La proprit name
Les objets Fonction contiennent des proprits utilisables.
Par exemple, le nom dune fonction est accessible en tant que proprit name :
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
Ce qui est drle, cest que la logique dattribution de noms est intelligente. Elle attribue galement le nom correct une fonction mme si elle est cre sans, puis immdiatement attribu :
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (il y a un nom !)
Cela fonctionne aussi si laffectation est faite avec une valeur par dfaut :
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (a marche !)
}
f();
Dans la spcification, cette fonctionnalit est appele contextual name. Si la fonction nen fournit pas, elle est dtermine partir du contexte lors de laffectation.
Les mthodes dobjet ont aussi des noms :
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
Cependant cest pas magique. Il y a des cas o il ny a aucun moyen de trouver le bon nom. Dans ce cas, la proprit name est vide, comme ci-dessous :
// fonction cre dans un tableau
let arr = [function() {}];
alert( arr[0].name ); // <chane de caractres vide>
// le moteur n'a aucun moyen de dfinir le bon nom. Donc, il n'y en a pas
Par contre, en pratique la plupart des fonctions ont un nom.
La proprit length
Il existe une autre proprit native, length, qui renvoie le nombre de paramtres 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 paramtres du reste ne sont pas compts.
La proprit length est parfois utilise pour la rflxion (introspection en anglais) dans des fonctions qui oprent sur dautres 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 quun utilisateur a fourni sa rponse, la fonction appelle les gestionnaires. Nous pouvons transmettre deux types de gestionnaires :
- Une fonction sans argument, qui nest appele que lorsque lutilisateur donne une rponse positive.
- Une fonction avec des arguments, appele dans les deux cas et renvoyant une rponse.
Pour appeler handler correctement, nous examinons la proprit handler.length.
Lide est que nous avons une syntaxe de gestionnaire simple, sans argument, pour les cas positifs (variante la plus frquente), 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 rponse positive, les deux gestionnaires sont appels
// pour une rponse ngative, seulement le second
ask("Question?", () => alert('You said yes'), result => alert(result));
Ceci est un cas particulier de ce quon appelle le polymorphism le traitement des arguments diffremment selon leur type ou, dans notre cas, en fonction de la length. Cette approche est utilise dans les bibliothques JavaScript.
Proprits personnalises
Nous pouvons galement ajouter nos propres proprits.
Nous ajoutons ici la proprit counter pour suivre le nombre total dappels :
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` ); // Appele 2 fois
Une proprit affecte une fonction comme sayHi.counter = 0 ne dfinit pas une variable locale counter lintrieur de celle-ci. En dautres termes, une proprit counter et une variable let counter sont deux choses indpendantes.
On peut traiter une fonction comme un objet, y stocker des proprits, mais cela na aucun effet sur son excution. Les variables ne sont pas des proprits de fonction et inversement. Ce sont des mondes parallles.
Les proprits de fonction peuvent parfois remplacer les fermetures. Par exemple, nous pouvons rcrire lexemple de fonction de compteur du chapitre Variable scope, closure pour utiliser une proprit 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 dutiliser une fermeture ?
La principale diffrence est que si la valeur de count rside dans une variable externe, le code externe ne peut pas y accder. Seules les fonctions imbriques peuvent le modifier. Et si cest 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 dpend donc de nos objectifs.
Fonction Expression Nomme (NFE)
Fonction Expression Nomme, 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 ralis quelque chose ici ? Quel est le but de ce nom supplmentaire "func" ?
Notons dabord que nous avons toujours une expression de fonction. Lajout du nom "func" aprs function nen a pas fait une dclaration de fonction, car il est toujours cr dans le cadre dune expression daffectation.
Lajout dun tel nom na 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 particularits propos du nom func, voici les raisons :
- Il permet la fonction de se rfrencer en interne.
- Il nest pas visible en dehors de la fonction.
Par exemple, la fonction sayHi ci-dessous sappelle 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'extrieur de la fonction)
Pourquoi utilisons-nous func ? Peut-tre juste utiliser sayHi pour lappel imbriqu ?
En fait, dans la plupart des cas, nous pouvons :
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
Le problme avec ce code est que sayHi peut changer dans le code externe. Si la fonction est assigne 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 ny a pas de sayHi local, donc la variable externe est utilise. Et au moment de lappel, ce sayHi extrieur est null.
Le nom optionnel que nous pouvons mettre dans lexpression de fonction est destin rsoudre exactement ce type de problmes.
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 nest pas pris de lextrieur (et non visible l-bas). La spcification garantit quelle fera toujours rfrence la fonction actuelle.
Le code externe a toujours sa variable sayHi ou welcome. Et func est un nom de fonction interne, la faon dont la fonction peut sappeler de manire fiable.
La fonctionnalit nom interne dcrite ici nest disponible que pour les expressions de fonction, pas pour les dclarations de fonction. Pour les dclarations de fonctions, il ny a aucune possibilit de syntaxe dajouter un nom interne supplmentaire.
Parfois, lorsque nous avons besoin dun nom interne fiable, cest la raison pour laquelle nous rcrivons une dclaration de fonction en tant quexpression de fonction nomme.
Rsum
Les fonctions sont des objets.
Ici nous avons couvert leurs proprits :
namele nom de la fonction. Habituellement tir de la dfinition de la fonction, mais sil nen existe pas, JavaScript essaie de le deviner partir du contexte (par exemple, une affectation).lengthle nombre darguments dans la dfinition de la fonction. Les paramtres du reste ne sont pas compts.
Si la fonction est dclare en tant quexpression de fonction (et non dans le flux du code principal) et quelle porte un nom, elle est appele expression de fonction nomme. Le nom peut tre utilis lintrieur pour se rfrencer, pour des appels rcursifs ou autres.
Les fonctions peuvent galement comporter des proprits supplmentaires. De nombreuses bibliothques JavaScript bien connues font un grand usage de cette fonctionnalit.
Elles crent une fonction principale et y attachent de nombreuses autres fonctions dassistance. Par exemple, la bibliothque jQuery cre une fonction nomme $. La bibliothque lodash cre une fonction _ et ajoute ensuite _.clone, _.keyBy et dautres proprits (voir la doc lorsque vous souhaitez en savoir plus leur sujet). En fait, elles le font pour rduire leur pollution de lespace global, de sorte quune seule bibliothque ne donne quune seule variable globale. Cela rduit la possibilit de conflits de noms.
Ainsi, une fonction peut faire un travail utile par elle-mme et aussi porter un tas dautres fonctionnalits dans les proprits.
Commentaires
<code>, pour plusieurs lignes enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen)