# Les fonctions
L'élément le plus important dans le langage JavaScript, c'est la fonction. Il faut donc maîtriser les fonctions en JavaScript : les différentes façons de les créer, et les différentes façon des les appeler (on dit aussi les invoquer ou encore les exécuter).
Une fonction, en JavaScript est un « citoyen de première classe » : il s'agit d'une valeur comme une autre, qui peut être stockée dans une variable, passée en paramètre à une autre fonction, ou être retournée par une fonction.
Une fonction est un objet (cf. plus loin pour les types en JavaScript), qui a un super pouvoir : celui de pouvoir être invoqué, d'être invocable (la spécification parle de "callable object"). On invoque une fonction en ajoutant des parenthèses à la fin de son nom.
Pour créer une fonction, il y a 3 possibilités (en fait 4, mais la quatrième est déconseillée, je n’en parlerai pas), avec chacune des nuances que nous verrons.
# Déclaration de fonction
La première est la déclaration de fonction :
function myFunction (unParametre, unAutreParametre) {
// Ici le corps de la fonction
}
Ici, la fonction doit absolument avoir un nom : elle ne peut pas être anonyme. Cette fonction prend 2 paramètres : unParametre
et unAutreParametre
.
Les fonctions déclarées avec le mot-clé function
sont « hissées » : elles seront disponibles dès la première ligne de code du bloc dans lequel elles sont déclarée, même si elles sont déclarées à la toute fin du bloc. Plus de détails juste après.
N.B. : par convention, en JavaScript, les noms de variables, de fonction, et de paramètres sont en camelCase. Sauf pour les constantes qui seront en SCREAMING_SNAKE_CASE, et les fonctions qui doivent être appelées en tant que constructeurs seront en PascalCase (comme camelCase mais avec la première lettre aussi en majuscule).
# Déclaration de variable et expression de fonction
On peut aussi déclarer une variable, et lui assigner une valeur qui est une fonction. Je déconseille fortement cette façon de faire : c'est inutilement verbeux, et peut dérouter les débutants certainement plus habitués aux déclarations de fonctions (cf. plus haut).
N.B. : Ici, on déclare uniquement la variable myFunction
. À droite de l'opérateur d'assignation (=
), il y a une expression de fonction, qui peut être anonyme (je déconseille aussi, même si, aujourd'hui, à l'exécution, l'environnement va donner un nom dynamiquement à cette fonction dans tous les cas, selon des règles un peu compliquées).
var myFunction = function optionalName () {
// Ici le corps de la fonction
}
# Les fonctions fléchées
Les fonctions fléchées sont une nouveauté de ES2015, et ajoute une syntaxe simplifiée pour les fonctions :
Pas de mot-clé fonction :
const noop = () => {} // Fonction "no-op" (no operation)
Les parenthèses pour les arguments sont obligatoires s'il y a aucun ou plus d'un argument, facultative si elle ne prend qu'un argument :
const log = msg => { console.log(msg) } // Fonction "log"
Le mot-clé return
et les accolades ne sont pas obligatoires s'il n'y a qu'une instruction :
const identity = id => id // Fonction "identity"
const add = (a, b) => a + b // Fonction d'addition
Les fonctions fléchées permettent une écriture simplifiée et très expressive des fonctions. Elles peuvent être déroutantes si on ne les connaît pas ou peu.
N.B. : Les fonctions fléchées n'ont pas de nouvelle liaison avec this
lors de leur exécution, contrairement aux fonctions créées avec le mot-clé function
(que ce soit en déclaration de fonction ou en expression de fonction). Plus de détails juste après le hissage.
# Le hissage des fonctions déclarées avec le mot-clé function
Au premier passage du moteur JS, c’est-à-dire avant exécution, le code suivant :
foo()
// Ceci est une déclaration de fonction
function foo () {
console.log('Yeah, it works!')
}
Est "transformé" en ce code :
function foo () {
console.log('Yeah, it works!')
}
foo()
C’est ce qu’on appelle le hissage (hosting) : les fonctions sont mises au début du code.
Attention : les expressions de fonctions ne sont pas hissées.
bar() // TypeError: undefined is not a function
// Ceci est une déclaration de variable foo,
// qui a pour valeur une fonction
var bar = function bar () { // Ici, il n'y a pas de déclaration de fonction,
// seulement une expression de fonction
console.log('Not working!')
}
Idem pour les fonctions fléchées (arrow functions (opens new window)) :
foo() // TypeError "undefined is not a function"
// fonction fléchée
var foo = () => console.log('Not working either :-(')
// { Expression de fonction }
# this
dans les fonctions
Le mot-clé this
en JavaScript est très déroutant pour les débutants, car la liaison avec ce mot-clé est dynamique : il dépend de la façon d’invoquer une fonction.
# TLDR :
Il existe 4 façons d'invoquer une fonction en JS :
- En tant que fonction (
this
vaut global object) - En tant que méthode (
this
vaut l’objet sur lequel on invoque la méthode) - En tant que constructeur (
this
vaut l’objet tout nouvellement créé) - Avec
Functiont.prototype.call
ouFunctiont.prototype.apply
(this
vaut le premier paramètre donnée àcall()
ouapply()
)
# En tant que fonction :
function foo () {
console.log(this)
}
foo() // this vaut global object :
// window dans un navigateur, global dans node.js
# En tant que méthode :
const elodie = {
name: 'Elodie'
greet: function () {
return 'Hi, I am ' + this.name
}
}
// Invocation de la fonction
elodie.greet() // 'Hi, I am Elodie'
// Évaluation de la propriété greet de elodie
elodie.greet // function () {}
const greet = elodie.greet
// Ici invocation en tant que fonction :
greet() // 'Hi, I am undefined' - window.name est undefined
# En tant que constructeur
function People (firstname) {
this.name = firstname
this.greet = function () {
return 'Hi, I am ' + this.name
}
}
const karim = new People('Karim')
karim.greet() // 'Hi, i am Karim'
Ici, lors de l'invocation en tant que constructeur, un objet vide est créé, et lié à this
. Implicitement, ce nouvel objet est renvoyé par le constructeur même s’il n’y a pas l’instruction return this
.
# avec call()
et apply()
Les méthodes call()
et apply()
du prototype de Function permettent d’indiquer explicitement à quoi doit être lié this
dans la fonction :
const stan = { name: 'Stan' }
greet.call(stan, arg1, arg2)
greet.apply(stan, [arg1, arg2])
greet.bind(stan)(arg1, arg2)
Ici, on peut invoquer n'importe quelle fonction en lui donnant explicitement à quoi on veut lier this
.
Dans le cas de call
et de apply
(qui sont sur le prototype de Function
, donc disponibles pour toutes les fonctions), on indique dans le premier argument ce qu'il faut que la fonction utilise comme valeur pour la lier à this
le temps de l'exécution.
La différence entre call
et apply
est dans le passage des arguments : apply
attend les arguments dans un tableau (Array
), alors que call
attend des arguments individuels.
Enfin, la méthode bind
, disponible aussi sur toutes les fonctions, permet de créer une nouvelle fonction, avec pour premier argument un objet auquel sera lié this
pour toutes les exécutions de cette nouvelle fonction.
# Particularité des fonctions fléchées
const elodie = {
name: 'Elodie',
firstname: 'Bouchez',
greet : function greed () {
const getFullname = () => this.name + ' ' + this.firstname
return 'Hi, I am ' + getFullname()
}
}
// Invocation de la méthode greet qui appelle la fonction getFullname()
elodie.greet() // 'Hi, I am Elodie Bouchez'
Même si la fonction getFullname()
est invoquée en tant que fonction, comme il n’y a pas de nouvelle liaison avec le mot-clé this
, this
dans le corps de la fonction fléchée vaut la même chose que ce qu’il vaut en dehors de la fonction. Il vaut donc ce que vaut this
lors de l’exécution de la méthode elodie.greet()
, qui est appelée ici en tant que méthode, et donc vaut l’objet contenu dans variable elodie
.
# Un mot sur le Prototype
Le prototype d'une fonction est un objet, partagé par tous les objets créés par cette fonction invoquée en tant que constructeur, et auquel est délégué la recherche (lookup) d'une propriété si elle ne se trouve pas sur l'objet. Si cette propriété n'est pas trouvée sur le prototype, elle est recherchée sur le prototype du prototype, et si besoin, le moteur JavaScript remonte jusqu'au dernier prototype de la chaîne de prototype.
Voici un exemple :
// Doit être appelée en tant que constructeur : PascalCase
function People (firstname) { // Création d'un objet vide
// et liaison (binding) de cet objet à this
this.name = firstname
} // cette fonction retourne implicitement this
// si elle est appelée avec new
const karim = new People('Karim')
// Ajout d'une propriété greet sur le prototype de People
People.prototype.greet = function greet () {
return 'Hi, I am ' + this.name
}
// Appel à la propriété greet et
// invocation de greet en tant que méthode de karim
karim.greet()
// greet n'est pas trouvé sur l'objet karim
// (karim n'a qu'une propriété : 'name')
// la recherche de greet est déléguée à son prototype
// et y est trouvée !
// La fonction est exécutée, et this vaut bien karim !
karim.toString()
// greet n'est pas trouvé sur l'objet karim
// (karim n'a qu'une propriété : 'name')
// la recherche de greet est délégué à son prototype
// et n'y est pas trouvée
// (le prototype de People n'a qu'une propriété : gr'eet)
// la recherche est déléguée au prototype du prototype de People
// C'est-à-dire le prototype de Object
// Object.prototype.toString existe !
// La fonction est exécutée, et this vaut toujours karim !
N.B. : La fonction greet peut être réécrite avec un « littéral de gabarit (opens new window) » (template strings (opens new window))
function greet () {
return `Hi, I am ${this.name}`
}
# Callback ou fonction de rappel
Très souvent, on utilise les fonctions en tant qu'argument dans les fonctions, par exemple ici :
document.addEventListener('click', function onClick (event) {
// On utilise le contenu de event ici
})
La fonction nommée onClick
(on aurait pu ne pas la nommer, puisque c'est une expression de fonction, mais c'est une mauvaise pratique) est une fonction de rappel, ou callback function ou tout simplement callback. Elle sera invoquée à chaque clic sur le document HTML.
Il arrive parfois qu'on doive invoquer une fonction de rappel à l'intérieur d'une autre fonction de rappel :
ajax(url, function onComplete(err, data) {
if (err) {
// Traitement de l'erreur ici
}
// Utilisation de data
const url2 = data.url
ajax(url2, function (err2, data2) {
if (err) {
// Traitement de l'erreur ici
}
})
})
Ceci mène à ce qu'on appelle l'enfer des callbacks ("callback hell (opens new window)")
La meilleure façon de quitter cet enfer est d'utiliser des bibliothèques qui fournissent une API avec des promesses (cf. plus loin).