Fonctions fléchées

Une expression de fonction fléchée (arrow function en anglais) permet d'avoir une syntaxe plus courte que les expressions de fonction et ne lie pas ses propres thisargumentssuper, ou new.target. Les fonctions fléchés sont obligatoirement anonymes et ne sont donc pas destinées à être utilisées pour déclarer des méthodes.

Syntaxe

([param] [, param]) => {
   instructions
}

(param1, param2, …, param2) => expression
// équivalent à 
(param1, param2, …, param2) => {
  return expression;
}

// Parenthèses non nécessaires quand il n'y a qu'un seul argument
param => expression

// Une fonction sans paramètre peut s'écrire avec un couple 
// de parenthèses ou un tiret bas (underscore)
() => { instructions }
_  => { instructions }

Des exemples plus détaillés, utilisant cette syntaxe, sont disponibles ici (en anglais).

param
Le nom d'un argument. S'il n'y a aucun argument, cela doit être indiqué par une paire de parenthèses (). S'il n'y a qu'un argument, les parenthèses ne sont pas nécessaires. (ex : toto => 1)
instructions ou expression
Plusieurs instructions doivent être encadrées par des accolades, {}. Une expression simple ne nécessite pas d'accolades. L'expression est également la valeur de retour implicite pour cette fonction.

Description

Deux facteurs sont à l'origine de la conception des fonctions fléchées : une syntaxe plus courte et l'absence de liaison pour this. Sur ce dernier point, cela signifie qu'une fonction fléchée ne lie pas son propre this au sein de la fonction (il en va de même avec arguments, super ou new.target).

Voir aussi l'article sur les fonctions fléchées présent sur https://tech.mozfr.org (l'article original en anglais est disponible ici).

Syntaxe plus courte

Pour des aspects fonctionnels, la légèreté de la syntaxe est bienvenue. Par exemple :

var a = [
  "We're up all night 'til the sun",
  "We're up all night to get some",
  "We're up all night for good fun",
  "We're up all night to get lucky"
];

// Sans la syntaxe des fonctions fléchées 
var a2 = a.map(function(s){ return s.length });

// Avec, on a quelque chose de plus concis
var a3 = a.map( s => s.length );

Pas de liaison pour this

Jusqu'a l'apparition des fonctions fléchées, chaque nouvelle fonction définissait son propre this (un nouvel objet dans le cas d'un constructeur, undefined dans les appels de fonctions stricts, le contexte de l'objet si la fonction est appelée comme une méthode, etc.). Cela a pu entraîner des confusions lorsqu'on utilisait un style de programmation orientée objet.

function Personne() {
  // Le constructeur Personne() définit `this` comme lui-même.
  this.âge = 0;

  setInterval(function grandir() {
    // En mode non strict, la fonction grandir() définit `this` 
    // comme l'objet global et pas comme le `this` defini 
    // par le constructeur Personne().
    this.âge++;
  }, 1000);
}

var p = new Personne();

Avec ECMAScript 3/5, ce problème a pu être résolu en affectant la valeur de this à une autre variable :

function Personne() {
  var that = this; 
  that.âge = 0;

  setInterval(function grandir() {
    // La fonction callback se réfère à la variable `that`
    // qui est le contexte souhaité
    that.âge++;
  }, 1000);
}

Autrement, on aurait pu utiliser une fonction de liaison afin que la bonne valeur this soit passée à la fonction grandir.

Les fonctions fléchées ne créent pas de nouveau contexte, elles capturent la valeur this de leur contexte. Le code qui suit fonctionne de la façon attendue :

function Personne(){
  this.âge = 0;

  setInterval(() => {
    this.âge++; // |this| réfère bien à l'objet personne
  }, 1000);
}

var p = new Personne();

Liens avec le mode strict

Ici this n'est pas liée, les règles du mode strict sont donc ignorées pour ce qui concerne this.

var f = () => {'use strict'; return this};
f() === window; // ou l'objet global

Le reste des règles du mode strict sont appliquées normalement.

Appel via Function.prototype.call() ou Function.prototype.apply()

Étant donné que this n'est pas lié, si on invoque une fonction via la méthode call ou apply, cela ne passera que des arguments mais n'aura aucun effet sur this :

var ajouter = {
  base: 1,

  add : function (a) {
    var f = v => v + this.base;
    return f(a);
  },

  addViaCall: function (a) {
    var f = v => v + this.base;
    var b = {
      base: 2
    };
    return f.call(b, a);
  }
};

console.log(ajouter.add(1));  // Cela affichera 2 dans la console
console.log(ajouter.addViaCall(1)) // Cela affichera toujours 2

Pas de liaison pour arguments

Les fonctions fléchées n'exposent pas d'objet arguments : arguments.length, arguments[0], arguments[1], et autres ne font donc pas référence aux arguments passés à la fonction fléchés. Dans ce cas arguments est simplement une référence à la variable de même nom si elle est présente dans la portée englobante :

var arguments = 42;
var arr = () => arguments;

arr(); // 42

function toto() {
  var f = (i) => arguments[0] + i; // lien implicite avec arguments de toto
  return f(2);
}

toto(1); // 3

Les fonctions fléchées n'ont donc pas leur propre objet arguments, mais dans la plupart des cas, les paramètres du reste représentent une bonne alternative :

function toto() { 
  var f = (...args) => args[0]; 
  return f(2); 
}

toto(1); // 2

Les fonctions fléchées comme méthodes

Comme indiqué précédemment, les fonctions fléchées sont mieux indiquées pour les fonctions qui ne sont pas des méthodes. Prenons un exemple pour illustrer ce point

'use strict';
var obj = {
  i: 10;
  b: () => console.log(this.i, this),
  c: function() {
    console.log(this.i, this);
  }
}

obj.b(); // affiche undefined, Window
obj.c(); // affiche 10, Object {...}

Utiliser le mot-clé yield

Le mot-clé yield ne peut pas être utilisé dans le corps d'une fonction fléchée (sauf si cela intervient dans une autre fonction, imbriquée dans la fonction fléchée). De fait, les fonctions fléchéees ne peuvent donc pas être utilisées comme générateurs.

Utiliser le mot-clé new

Les fonctions fléchées ne peuvent pas être utilisées comme constructeurs et lèveront une exception si elles sont utilisées avec le mot-clé new.

Gestion du corps de la fonction

Les fonctions fléchées peuvent avoir une syntaxe concise ou utiliser un bloc d'instructions classique. Cette dernière syntaxe n'a pas de valeur de retour implicite et il faut donc employer l'instruction return.

var func = x => x * x;                // méthode concise, retour implicite
var func = (x, y) => { return x + y; }// bloc classique, retour explicite

Renvoyer des littéraux objets

Attention à bien utiliser les parenthèses lorsqu'on souhaite renvoyer des objets avec des littéraux :

var func = () => { toto: 1 }; // func() renverra undefined !
var func2 = () =>  { toto: function() {} }; // SyntaxError

En effet, ici, l'analyse de l'expression trouve des blocs d'instructions au lieu de littéraux objets. Pour éviter cet effet indésirable, on pourra encadrer le littéral objet :

var func = () => ({ toto: 1 });

Sauts de ligne

Il ne peut pas y avoir de saut de ligne entre les paramètres et la flèche d'une fonction fléchée.

var func = ()
            => 1; // SyntaxError: expected expression, got '=>'

Ordre syntaxique

La flèche utilisée pour une fonction fléchée n'est pas un opérateur. Les fonctions fléchées ont des règles spécifiques quant à leur place dans la syntaxe et intéragissent différemment de la précédence des opérateurs par rapport à une fonction classique :

let callback;

callback = callback || function() {}; // o
callback = callback || () => {};      // SyntaxError: invalid arrow-function arguments
callback = callback || (() => {});    // OK

Exemples

// Une fonction fléchée vide renvoie undefined
let vide = () => {};

(() => "tototruc")() // exemple d'une fonction immédiatement invoquée qui
                     // renvoie "tototruc" 

var simple = a => a > 15 ? 15 : a; 
simple(16); // 15
simple(10); // 10

var complexe = (a, b) => {
  if (a > b) {
    return a;
  } else {
    return b;
  }
}

var arr = [5, 6, 13, 0, 1, 18, 23];
var sum = arr.reduce((a, b) => a + b);  // 66
var even = arr.filter(v => v % 2 == 0); // [6, 0, 18]
var double = arr.map(v => v * 2);       // [10, 12, 26, 0, 2, 36, 46]

// On peut aussi construire des chaînes de promesses
// plus concises
promise.then(a => {
  // ...
}).then(b => {
  // ...
});

// Cela permet de visualiser les 
// fonctions sans paramètres
setTimeout( _ => {
  console.log("Et voilà");
  setTimeout( _ => {
    console.log("ensuite…");
  }, 1);
}, 1);

Spécifications

Spécification État Commentaires
ECMAScript 2015 (6th Edition, ECMA-262)
La définition de 'Arrow Function Definitions' dans cette spécification.
Standard Définition initiale.
ECMAScript 2017 Draft (ECMA-262)
La définition de 'Arrow Function Definitions' dans cette spécification.
Projet  

Compatibilité des navigateurs

Fonctionnalité Chrome Firefox (Gecko) Edge Internet Explorer Opera Safari
Support simple 45.0 22.0 (22.0) (Oui) Pas de support 32 10
Fonctionnalité Android Webview Android Firefox Mobile (Gecko) IE Mobile Opera Mobile Safari Mobile Chrome pour Android
Support simple Pas de support 45.0 22.0 (22.0) Pas de support Pas de support 10 45.0

Notes relatives à Firefox

  • L'implémentation initiale des fonctions fléchées était automatiquement stricte sur Firefox. Cela a été changé avec Firefox 24. Il est désormais nécessaire d'utiliser "use strict";.
  • La sémantique des fonctions fléchées est différente de celle utilisée par les expressions de fermetures ajoutées à Firefox 3 (détails : Javascript 1.8) car pour ces dernières, this n'est pas lié lexicalement.
  • Avant Firefox 39, il était possible d'utiliser une fin de ligne après les arguments d'une fonction fléchée. Cela était incorrect par rapport aux spécifications ES6 et a été réparé. Ainsi, du code tel que () \n => {} déclenchera une exception SyntaxError à partir de cette version (incluse).

Voir aussi

  • L'article sur les fonctions fléchées présent sur https://tech.mozfr.org (l'article original en anglais est disponible ici).

Étiquettes et contributeurs liés au document

 Contributeurs à cette page : SphinxKnight, Buzut, tdd, warpdesign, BenoitEsnard, fscholz
 Dernière mise à jour par : SphinxKnight,