Les fat arrow function en Javascript

Autre nouveauté apportée par ES2015, les fat arrow functions n’en ont pas l’air, mais constitue un gros changement dans notre façon de développer.

Si nous voulons être très grossier dans notre explication — mais cela donnera une base à notre article — nous pouvons dire que les fat arrow functions sont une nouvelle façon d’écrire les fonctions anonymes.

/* fonction anonymes classique */
let multiple = function (value) {
  return value * 10
}

/* fat arrow function */
let multiple = value => value * 10

Les deux écritures ci-dessus sont équivalentes.

Écriture des fat arrow functions

Les fat arrow functions s’écrivent de différentes façons en fonction des situations et des préférences du développeur.

L’écriture utilisée dans le premier exemple est la forme la plus épurée des fat arrow functions. Voici toutes les formes :

  • value => value * 10
  • (value) => { return value * 10 }
  • value => { return value * 10 }
  • (value) => value * 10

Vous l’aurez compris, toutes les variantes sont équivalentes. Pour savoir si vous êtes obligé de mettre des parenthèses ou des accolades, retenez ceci :

  • Les parenthèses autour des paramètres sont obligatoires si vous ne passez aucun ou plus d’un paramètre.
  • Les accolades autour du corps de la fonction sont obligatoires si vous souhaitez exécuter plusieurs instructions ou que vous ne souhaitez pas retourner la valeur produite par votre unique instruction.

"Ne souhaitez pas retourner la valeur produite par votre instruction" ? Oui, les plus attentifs l’on sûrement remarqué : les versions sans accolades sont dépourvues de return, pourtant elle retourne bien une valeur.

Lorsque les accolades ne sont pas utilisées, le return est induit. La valeur de retour est la valeur produite par l’instruction (ici value * 10).

Cette nouvelle syntaxe est beaucoup plus concise que celle des fonctions anonymes classiques. Par contre, elle peut se révéler plus difficile à lire dans certains cas :

let closure = a => b => (a * b) % 5 === 0 

Prenez donc bien garde à l’écriture que vous choisissez et veillez à ce que vos collègues ne passent pas 3/4 d’heure à essayer de comprendre votre code.

Pour ceux qui veulent comprendre l’exemple ci-dessus lisez le code suivant :

let closure = function (a) {
  return function (b) {
    let c = a * b
    let d = c % 5

    if (d === 0) {
      return true
    }

    return false
  }
}

Oui, je sais, j’abuse.

Les différences avec les fonctions anonymes classiques

Mis à part la belle syntaxe, quel est l’intérêt des arrow functions ? Quelle est la différence avec la syntaxe classique ?

La grosse différence réside dans le this.

Par défaut, une fonction anonyme classique — et même dans une fonction nommée —, a sa propre valeur de this qui dépend du contexte d’exécution. Lorsque cette fonction est appelée dans le contexte global, this est égal a "window" mais lorsqu’elle est appelée dans un objet this est égal à cet objet.

let thisIsWindow = function () {
  return this === window
}
thisIsWindow() // true

let obj = {
  thisIsWindow
}
obj.thisIsWindow() // false

let thisIsWindowBis = obj.thisIsWindow
thisIsWindowBis() // true

C’est à chaque fois la même fonction, mais exécutée dans des contextes différents.

C’est un fonctionnement normal en JavaScript. Mais ça commence à devenir gênant lorsque j’utilise la méthode d’un objet A dans un objet B, cette méthode demande un callback. Que vaut this dans ce callback ?

let obj = {
  a: 'test',
  thisIsWindow() {
    setTimout(function () {
      console.log(this.a) // BUG !
    }, 1000)
  }
}

Alors que je m’attends a ce que this soit égal à obj, ici il vaut window. Pourquoi ?

Le fautif est le mode de fonctionnement des fonctions et de this en JavaScript. Lorsqu’elle sera exécutée la fonction anonyme le sera dans setTimeout() et les fonctions natives ne faisant pas référence à un objet sont des méthodes de l'objet window (la fonction setTimeout() est en réalité window.setTimeout()).

La fonction anonyme sera exécutée dans window, le this de cette fonction vaut donc windows.

Les solutions proposées jusqu’alors étaient let that = this et .bind(). Aujourd’hui la solution proposée par ES2015 est les fat arrow functions.

Le this des fat arrow functions dépend du contexte de déclaration. En fait, elles n’ont pas de this propre, mais l’hérite du contexte de déclaration parent.

/* Syntaxe ES5 */
let obj = {
  a: 'test',
  thisIsWindow() {
    let self = this
    setTimout(function () { console.log(self.a) }, 1000)
  }
}

/* Syntaxe ES6 */
let obj = {
  a: 'test',
  thisIsWindow() {
    setTimout(_ => { console.log(this.a) }, 1000)
  }
}

L’intérêt ne se voit pas forcément sur un petit exemple comme celui-ci, mais croyez-moi, sur une base de code de plusieurs milliers de lignes ça fait toute la différence lorsque vous n’avez pas à relire tout le fichier en sens inverse pour être sûr que c’est le bon this que vous stockez dans le that.

Conclusion

Personnellement, c’est devenu ma méthode de création de fonctions anonymes par défaut. Je n’utilise la syntaxe function () {} seulement quand j’en ai besoin.

Cela m’évite bien des problèmes. Et comme à la fin mon code est plus beau ça me va.


Pour aller plus loin :