web-dev-qa-db-fra.com

Quand devrais-je utiliser les fonctions de flèche dans ECMAScript 6?

La question s'adresse aux personnes qui ont réfléchi au style de code dans le contexte du prochain ECMAScript 6 (Harmony) et qui ont déjà travaillé avec cette langue.

Avec () => {} et function () {}, nous obtenons deux méthodes très similaires pour écrire des fonctions dans ES6. Dans d'autres langues, les fonctions lambda se distinguent souvent par leur anonymat, mais dans ECMAScript, toute fonction peut être anonyme. Chacun des deux types a des domaines d'utilisation uniques (notamment lorsque this doit être lié explicitement ou explicitement ne pas être lié). Entre ces domaines, il existe un grand nombre de cas où l'une ou l'autre des notations fera l'affaire.

Les fonctions de flèche dans ES6 ont au moins deux limitations:

  • Ne fonctionne pas avec new
  • Correction de this lié à la portée lors de l'initialisation

Mis à part ces deux limitations, les fonctions de flèche pourraient théoriquement remplacer les fonctions standard presque n'importe où. Quelle est la bonne approche en les utilisant dans la pratique? Les fonctions de flèche doivent-elles être utilisées, par exemple:

  • "partout où ils fonctionnent", c’est-à-dire partout une fonction ne doit pas nécessairement être agnostique à propos de la variable this et nous ne créons pas d’objet.
  • seulement "partout où ils sont nécessaires", c'est-à-dire des écouteurs d'événements, des délais d'expiration, qui doivent être liés à une certaine portée
  • avec des fonctions 'courtes' mais pas avec des fonctions 'longues'
  • uniquement avec des fonctions ne contenant pas d'autre fonction de flèche

Ce que je recherche, c’est un guide pour choisir la notation de fonction appropriée dans la future version d’ECMAScript. La ligne de conduite devra être claire, pour pouvoir être enseignée aux développeurs au sein d'une équipe, et cohérente pour ne pas nécessiter de refactorisation constante d'une notation de fonction à une autre.

372
lyschoening

Il y a quelque temps, notre équipe a migré tout son code (une application AngularJS de taille moyenne) vers JavaScript compilé à l'aide de Traceur Babel . J'utilise maintenant la règle empirique suivante pour les fonctions de ES6 et des versions ultérieures:

  • Utilisez function dans la portée globale et pour les propriétés Object.prototype.
  • Utilisez class pour les constructeurs d'objet.
  • Utilisez => partout ailleurs.

Pourquoi utiliser les fonctions de flèche presque partout?

  1. Sécurité du contenu: lorsque les fonctions de flèche sont utilisées de manière cohérente, il est garanti que tout utilise le même thisObject que la racine. Si même un seul rappel de fonction standard est mélangé avec un tas de fonctions de flèche, il y a un risque que la portée devienne fausse.
  2. Compacité: les fonctions de flèche sont plus faciles à lire et à écrire. (Cela peut sembler une opinion alors je donnerai quelques exemples plus loin).
  3. Clarté: Lorsque presque tout est une fonction de flèche, tout function normal se démarque immédiatement pour définir la portée. Un développeur peut toujours rechercher l'instruction function immédiatement supérieure pour savoir ce qu'est thisObject.

Pourquoi toujours utiliser des fonctions standard sur la portée globale ou la portée du module?

  1. Pour indiquer une fonction qui ne devrait pas accéder à thisObject.
  2. L'objet window (portée globale) est mieux traité explicitement.
  3. De nombreuses définitions Object.prototype résident dans la portée globale (pensez String.prototype.truncate etc.) et celles-ci doivent généralement être de type function de toute façon. L'utilisation constante de function sur la portée globale permet d'éviter les erreurs.
  4. De nombreuses fonctions de la portée globale sont des constructeurs d’objets pour les définitions de classe d’ancien style.
  5. Les fonctions peuvent être nommées1. Cela présente deux avantages: (1) Il est moins gênant d’écrirefunction foo(){} que const foo = () => {} - en particulier en dehors d’autres appels de fonctions. (2) Le nom de la fonction apparaît dans les traces de la pile. Bien qu'il soit fastidieux de nommer chaque rappel interne, il est probablement judicieux de nommer toutes les fonctions publiques.
  6. Les déclarations de fonction sont hoisted , (ce qui signifie qu'elles peuvent être consultées avant d'être déclarées), attribut utile dans une fonction d'utilitaire statique.


Constructeurs d'objets

Tenter d'instancier une fonction de flèche lève une exception:

var x = () => {};
new x(); // TypeError: x is not a constructor

L’un des principaux avantages des fonctions par rapport aux fonctions fléchées est donc que les fonctions sont doubles en tant que constructeurs d’objets:

function Person(name) {
    this.name = name;
}

Cependant, le même fonctionnement2 ES Harmony projet de définition de classe est presque aussi compact:

class Person {
    constructor(name) {
        this.name = name;
    }
}

Je m'attends à ce que l'utilisation de l'ancienne notation finisse par être découragée. La notation du constructeur d’objet peut toujours être utilisée par certains pour de simples fabriques d’objets anonymes dans lesquelles les objets sont générés par programme, mais pas pour beaucoup d’autres choses.

Lorsqu'un constructeur d'objet est nécessaire, envisagez de convertir la fonction en class, comme indiqué ci-dessus. La syntaxe fonctionne également avec les fonctions/classes anonymes.


Lisibilité des fonctions de flèche

Le meilleur argument en faveur de l’adhérence aux fonctions habituelles - la sécurité de la portée de l’application - serait que les fonctions de flèche soient moins lisibles que les fonctions classiques. Si votre code n'est pas fonctionnel au départ, les fonctions de flèche peuvent ne pas sembler nécessaires et, si elles ne sont pas utilisées de manière cohérente, leur apparence est laide.

ECMAScript a quelque peu changé depuis qu'ECMAScript 5.1 nous a donné le Array.forEach, Array.map fonctionnel et toutes ces fonctionnalités de programmation fonctionnelles qui nous font utiliser des fonctions pour lesquelles des boucles for-loop auraient déjà été utilisées. JavaScript asynchrone a décollé un peu. ES6 livrera également un objet Promise , ce qui signifie encore plus de fonctions anonymes. Il n'y a pas de retour en arrière pour la programmation fonctionnelle. En JavaScript fonctionnel, les fonctions de flèche sont préférables aux fonctions habituelles.

Prenez par exemple ce morceau de code (particulièrement déroutant)3:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(articles => Promise.all(articles.map(article => article.comments.getList())))
        .then(commentLists => commentLists.reduce((a, b) => a.concat(b)));
        .then(comments => {
            this.comments = comments;
        })
}

Le même morceau de code avec des fonctions régulières:

function CommentController(articles) {
    this.comments = [];

    articles.getList()
        .then(function (articles) {
            return Promise.all(articles.map(function (article) { 
                return article.comments.getList();
            }));
        })
        .then(function (commentLists) {
            return commentLists.reduce(function (a, b) {
                return a.concat(b); 
            });
        })
        .then(function (comments) {
            this.comments = comments;
        }.bind(this));
}

Chacune des fonctions de flèche peut être remplacée par une fonction standard, mais il y aurait très peu à gagner à le faire. Quelle version est la plus lisible? Je dirais le premier.

Je pense que la question d'utiliser des fonctions de flèche ou des fonctions régulières deviendra moins pertinente avec le temps. La plupart des fonctions deviendront soit des méthodes de classe, supprimant le mot clé function, soit deviendront des classes. Les fonctions resteront utilisées pour patcher les classes via le Object.prototype. En attendant, je suggère de réserver le mot clé function à tout ce qui devrait réellement être une méthode de classe ou une classe.


Notes

  1. Les fonctions fléchées nommées ont été différées dans la spécification ES6 . Ils pourraient encore être ajouté une version future.
  2. Selon le projet de spécification , "Les déclarations/expressions de classe créent une paire constructeur/fonction prototype exactement comme pour les déclarations de fonction" tant qu'une classe n'utilise pas le mot clé extend. Une différence mineure est que les déclarations de classe sont des constantes, alors que les déclarations de fonctions ne le sont pas.
  3. Remarque sur les blocs dans les fonctions de flèche à instruction unique: J'aime utiliser un bloc chaque fois qu'une fonction de flèche est appelée pour l'effet secondaire uniquement (par exemple, une affectation). De cette façon, il est clair que la valeur de retour peut être ignorée.
303
lyschoening

Selon la proposition , les flèches avaient pour but "de traiter et de résoudre plusieurs problèmes courants liés aux méthodes traditionnelles Function Expression.". Ils avaient l'intention d'améliorer les choses en liant this de manière lexicale et en proposant une syntaxe abrégée.

Cependant,

  • On ne peut pas toujours lier this lexicalement
  • La syntaxe de la fonction flèche est délicate et ambiguë

Par conséquent, les fonctions de flèche créent des risques de confusion et d'erreurs et doivent être exclues du vocabulaire d'un programmeur JavaScript et remplacées par function exclusivement.

Concernant le lexical this

this est problématique:

function Book(settings) {
    this.settings = settings;
    this.pages = this.createPages();
}
Book.prototype.render = function () {
    this.pages.forEach(function (page) {
        page.draw(this.settings);
    }, this);
};

Les fonctions fléchées ont pour but de résoudre le problème où nous devons accéder à une propriété de this à l'intérieur d'un rappel. Il y a déjà plusieurs façons de le faire: on peut affecter this à une variable, utiliser bind ou utiliser le troisième argument disponible sur les méthodes d'agrégat Array. Cependant, les flèches semblent être la solution de contournement la plus simple, aussi la méthode pourrait-elle être refactorisée comme suit:

this.pages.forEach(page => page.draw(this.settings));

Cependant, considérez si le code utilise une bibliothèque telle que jQuery, dont les méthodes lient this spécialement. Maintenant, il y a deux valeurs this à traiter:

Book.prototype.render = function () {
    var book = this;
    this.$pages.each(function (index) {
        var $page = $(this);
        book.draw(book.currentPage + index, $page);
    });
};

Nous devons utiliser function pour que each puisse lier this de manière dynamique. Nous ne pouvons pas utiliser une fonction de flèche ici.

Traiter avec plusieurs valeurs this peut également être source de confusion, car il est difficile de savoir de quelle this un auteur parlait:

function Reader() {
    this.book.on('change', function () {
        this.reformat();
    });
}

L'auteur avait-il réellement l'intention d'appeler Book.prototype.reformat? Ou a-t-il oublié de lier this et a-t-il l'intention d'appeler Reader.prototype.reformat? Si nous changeons le gestionnaire en une fonction de flèche, nous nous demanderons de la même manière si l'auteur voulait la dynamique this, mais a choisi une flèche car elle correspond à une ligne:

function Reader() {
    this.book.on('change', () => this.reformat());
}

On peut se poser la question suivante: "Est-il exceptionnel que les flèches soient parfois la mauvaise fonction à utiliser? Peut-être que si nous avons rarement besoin de valeurs dynamiques this, il serait toujours correct d'utiliser des flèches la plupart du temps."

Mais posez-vous la question suivante: "Cela vaut-il la peine de déboguer le code et de constater que le résultat d'une erreur a été provoqué par un" cas Edge? "" Je préférerais éviter les problèmes non seulement la plupart du temps, mais aussi 100% du temps.

Il existe un meilleur moyen: utilisez toujours function (pour que this puisse toujours être lié dynamiquement) et faites toujours référence à this via une variable. Les variables sont lexicales et prennent beaucoup de noms. Assigner this à une variable clarifiera vos intentions:

function Reader() {
    var reader = this;
    reader.book.on('change', function () {
        var book = this;
        book.reformat();
        reader.reformat();
    });
}

De plus, toujours assigner this à une variable (même s'il y a une seule this ou aucune autre fonction) assure que ses intentions restent claires même après le code est changé.

De plus, la dynamique this n'est guère exceptionnelle. jQuery est utilisé sur plus de 50 millions de sites Web (au moment d'écrire ces lignes en février 2016). Voici d'autres API liant this de manière dynamique:

  • Mocha (~ 120k téléchargements hier) expose des méthodes pour ses tests via this.
  • Grunt (~ 63k téléchargements hier) expose les méthodes pour les tâches de construction via this.
  • Le backbone (~ 22k téléchargements hier) définit les méthodes accédant à this.
  • Les API d'événement (comme les DOM) font référence à un EventTarget avec this.
  • Les API prototypes qui sont corrigées ou étendues font référence à des instances avec this.

(Statistiques via http://trends.builtwith.com/javascript/jQuery et https://www.npmjs.com .)

Vous aurez probablement besoin de liaisons dynamiques this déjà.

Un this lexical est parfois attendu, mais parfois pas; de même qu'une dynamique this est parfois attendue, mais parfois pas. Heureusement, il existe un meilleur moyen, qui produit et communique toujours la liaison attendue.

Concernant la syntaxe abrégée

Les fonctions fléchées ont réussi à fournir une "forme syntaxique plus courte" pour les fonctions. Mais ces fonctions plus courtes vous rendront-elles plus performantes?

x => x * x est-il "plus facile à lire" que function (x) { return x * x; }? Peut-être est-ce le cas, car il est plus susceptible de produire une seule ligne de code courte. En accord avec Dyson Influence de la vitesse de lecture et de la longueur de ligne sur l’efficacité de la lecture à l’écran ,

Une longueur de ligne moyenne (55 caractères par ligne) semble permettre une lecture efficace à des vitesses normales et rapides. Cela a produit le plus haut niveau de compréhension. . .

Des justifications similaires sont apportées pour l'opérateur conditionnel (ternaire) et pour les instructions if à une seule ligne.

Cependant, êtes-vous écrit réellement les fonctions mathématiques simples annoncé dans la proposition ? Mes domaines ne sont pas mathématiques, alors mes sous-programmes sont rarement aussi élégants. Au lieu de cela, je vois souvent les fonctions de flèche dépasser une limite de colonne et passer à une autre ligne en raison de l'éditeur ou du guide de style, ce qui annule la "lisibilité" de la définition de Dyson.

On pourrait poser: "Pourquoi ne pas utiliser la version courte pour des fonctions courtes, lorsque cela est possible?" Mais à présent, une règle stylistique contredit une contrainte de langage: "Essayez d’utiliser la notation de fonction la plus courte possible, en gardant à l’esprit que seule la notation la plus longue lie parfois this comme prévu." Une telle confusion rend les flèches particulièrement sujettes aux abus.

La syntaxe des fonctions de flèche soulève de nombreux problèmes:

const a = x =>
    doSomething(x);

const b = x =>
    doSomething(x);
    doSomethingElse(x);

Ces deux fonctions sont syntaxiquement valides. Mais doSomethingElse(x); ne figure pas dans le corps de b, il s'agit simplement d'une déclaration de bas niveau indentée.

En développant la forme de bloc, il n'y a plus de return implicite, que l'on pourrait oublier de restaurer. Mais l'expression peut seulement == était destinée à produire un effet secondaire, alors qui sait si un return explicite sera nécessaire pour aller de l'avant?

const create = () => User.create();

const create = () => {
    let user;
    User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

const create = () => {
    let user;
    return User.create().then(result => {
        user = result;
        return sendEmail();
    }).then(() => user);
};

Ce qui peut être conçu comme paramètre de repos peut être analysé en tant qu'opérateur de propagation:

processData(data, ...results => {}) // Spread
processData(data, (...results) => {}) // Rest

L'affectation peut être confondue avec les arguments par défaut:

const a = 1;
let x;
const b = x => {}; // No default
const b = x = a => {}; // "Adding a default" instead creates a double assignment
const b = (x = a) => {}; // Remember to add parens

Les blocs ressemblent à des objets:

(id) => id // Returns `id`
(id) => {name: id} // Returns `undefined` (it's a labeled statement)
(id) => ({name: id}) // Returns an object

Qu'est-ce que ça veut dire?

() => {}

L'auteur avait-il l'intention de créer un no-op ou une fonction qui renvoie un objet vide? (Dans cet esprit, devrions-nous jamais placer { après =>?? Devrions-nous nous limiter à la syntaxe d'expression uniquement? Cela réduirait encore la fréquence des flèches.)

=> ressemble à <= et >=:

x => 1 ? 2 : 3
x <= 1 ? 2 : 3

if (x => 1) {}
if (x >= 1) {}

Pour invoquer immédiatement une expression de fonction de flèche, vous devez placer () à l'extérieur, mais placer () à l'intérieur est valide et peut être intentionnelle.

(() => doSomething()()) // Creates function calling value of `doSomething()`
(() => doSomething())() // Calls the arrow function

Bien que, si on écrit (() => doSomething()()); avec l'intention d'écrire une expression de fonction immédiatement invoquée, rien ne se passera.

Il est difficile d'affirmer que les fonctions fléchées sont "plus compréhensibles" compte tenu de tous les cas susmentionnés. Un pourrait apprendre toutes les règles spéciales requises pour utiliser cette syntaxe. ça en vaut vraiment la peine?

La syntaxe de function est généralisée de façon peu commune. Utiliser exclusivement function signifie que le langage lui-même empêche d'écrire du code déroutant. Pour écrire des procédures dont la syntaxe doit être comprise dans tous les cas, je choisis function.

Concernant une ligne directrice

Vous demandez une ligne directrice qui doit être "claire" et "cohérente". L'utilisation de fonctions fléchées aboutira éventuellement à un code non valide du point de vue de la syntaxe, logiquement non valide, les deux formes de fonctions étant entrelacées, de manière significative et de manière arbitraire. Par conséquent, je propose ce qui suit:

Guide pour la notation de fonction dans ES6:

  • Toujours créer des procédures avec function.
  • Toujours affecter this à une variable. N'utilisez pas () => {}.
79
Jackson

Les fonctions flèche ont été créées pour simplifier la fonction scope et résoudre le mot clé this en le simplifiant davantage. Ils utilisent la syntaxe =>, qui ressemble à une flèche.

Remarque: il ne remplace pas les fonctions existantes. Si vous remplacez chaque syntaxe de fonction par des fonctions de flèche, cela ne fonctionnera pas dans tous les cas.

Examinons la syntaxe ES5 existante. Si le mot clé this était contenu dans une méthode d'objet (une fonction appartenant à un objet), à quoi ferait-il référence?

var Actor = {
  name: 'RajiniKanth',
  getName: function() {
     console.log(this.name);
  }
};
Actor.getName();

L'extrait ci-dessus ferait référence à un object et afficherait le nom "RajiniKanth". Examinons l'extrait ci-dessous et voyons ce que cela signifierait ici.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Maintenant, qu'en est-il si le mot clé this était à l'intérieur de method’s function?

Ici, cela ferait référence à window object plutôt qu'à inner function comme étant tombé de scope. Parce que this, fait toujours référence au propriétaire de la fonction dans laquelle elle se trouve, puisqu'elle est maintenant hors de portée, l'objet window/global.

Lorsqu'il se trouve à l'intérieur d'une méthode de object, le propriétaire de function est l'objet. Ainsi, le mot-clé this est lié à l'objet. Pourtant, lorsqu'il se trouve dans une fonction, qu'il soit autonome ou dans une autre méthode, il fera toujours référence à l'objet window/global.

var fn = function(){
  alert(this);
}

fn(); // [object Window]

Il y a des façons de résoudre ce problème dans notre ES5 lui-même, examinons-le avant de plonger dans les fonctions de flèche ES6 pour le résoudre.

Généralement, vous créez une variable en dehors de la fonction interne de la méthode. Maintenant, la méthode ‘forEach’ obtient l'accès à this et donc aux propriétés object’s et à leurs valeurs.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   var _this = this;
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

en utilisant bind pour associer le mot clé this qui fait référence à la méthode du method’s inner function.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach(function(movie) {
     alert(_this.name + " has acted in " + movie);
   }).bind(this);
  }
};

Actor.showMovies();

Maintenant, avec la fonction de flèche ES6, nous pouvons traiter le problème de lexical scoping d'une manière plus simple.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  showMovies: function() {
   this.movies.forEach((movie) => {
     alert(this.name + " has acted in " + movie);
   });
  }
};

Actor.showMovies();

Arrow functions ressemble plus à des instructions de fonction, sauf qu'elles bind le ceci à parent scope. Si l'argument arrow function is in top scope, this fait référence à window/global scope, alors qu'une fonction de flèche dans une fonction régulière aura son argument identique à sa fonction externe.

Avec arrow les fonctions this sont liées au scope qui les entoure au moment de la création et ne peuvent pas être modifiées. Les nouveaux opérateurs, bind, call et apply n'ont aucun effet sur cela.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

// With a traditional function if we don't control
// the context then can we lose control of `this`.
var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`
  asyncFunction(o, function (param) {
  // We made a mistake of thinking `this` is
  // the instance of `o`.
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? false

Dans l'exemple ci-dessus, nous avons perdu le contrôle de ceci. Nous pouvons résoudre l'exemple ci-dessus en utilisant une référence de variable de this ou en utilisant bind. Avec ES6, il devient plus facile de gérer la this comme étant liée à lexical scoping.

var asyncFunction = (param, callback) => {
  window.setTimeout(() => {
  callback(param);
  }, 1);
};

var o = {
  doSomething: function () {
  // Here we pass `o` into the async function,
  // expecting it back as `param`.
  //
  // Because this arrow function is created within
  // the scope of `doSomething` it is bound to this
  // lexical scope.
  asyncFunction(o, (param) => {
  console.log('param === this?', param === this);
  });
  }
};

o.doSomething(); // param === this? true

Quand ne pas utiliser les fonctions de flèche

À l'intérieur d'un littéral d'objet.

var Actor = {
  name: 'RajiniKanth',
  movies: ['Kabali', 'Sivaji', 'Baba'],
  getName: () => {
     alert(this.name);
  }
};

Actor.getName();

Actor.getName est défini avec une fonction de flèche, mais il est alerté non défini car this.name est undefined car le contexte reste à window.

Cela se produit parce que la fonction de flèche lie le contexte lexicalement avec le window object... c'est-à-dire la portée externe. L'exécution de this.name équivaut à window.name, qui n'est pas défini.

Prototype d'objet

La même règle s'applique lors de la définition de méthodes sur un prototype object. Au lieu d'utiliser une fonction de flèche pour définir la méthode sayCatName, ce qui donne un context window incorrect:

function Actor(name) {
  this.name = name;
}
Actor.prototype.getName = () => {
  console.log(this === window); // => true
  return this.name;
};
var act = new Actor('RajiniKanth');
act.getName(); // => undefined

Invoquer des constructeurs

this dans un appel de construction est le nouvel objet créé. Lors de l'exécution de new Fn (), le contexte du constructor Fn est un nouvel objet: this instanceof Fn === true.

this est configuré à partir du contexte englobant, c'est-à-dire de la portée externe, ce qui le rend non affecté au nouvel objet créé.

var Message = (text) => {
  this.text = text;
};
// Throws "TypeError: Message is not a constructor"
var helloMessage = new Message('Hello World!');

Rappel avec contexte dynamique

La fonction flèche lie la context de manière statique lors de la déclaration et il n'est pas possible de la rendre dynamique. L'association d'écouteurs d'événement à des éléments DOM est une tâche courante dans la programmation côté client. Un événement déclenche la fonction de gestionnaire avec cet élément cible.

var button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log(this === window); // => true
  this.innerHTML = 'Clicked button';
});

this est une fenêtre dans une fonction de flèche définie dans le contexte global. Lorsqu'un événement click se produit, le navigateur essaie d'appeler la fonction de gestionnaire avec un contexte de bouton, mais la fonction de flèche ne modifie pas son contexte prédéfini. this.innerHTML est équivalent à window.innerHTML et n'a pas de sens.

Vous devez appliquer une expression de fonction, qui permet de changer cela en fonction de l'élément cible:

var button = document.getElementById('myButton');
button.addEventListener('click', function() {
  console.log(this === button); // => true
  this.innerHTML = 'Clicked button';
});

Lorsque l'utilisateur clique sur le bouton, celui-ci dans la fonction de gestionnaire est un bouton. Ainsi, this.innerHTML = 'Clicked button' modifie correctement le texte du bouton pour refléter le statut cliqué.

Références: https://dmitripavlutin.com/when-not-to-use-arrow-functions-in-javascript/

34
Thalaivar

fonctions de flèche - fonctionnalité ES6 la plus largement utilisée à ce jour ...

Utilisation: Toutes les fonctions ES5 doivent être remplacées par des fonctions fléchées ES6, sauf dans les scénarios suivants:

Les fonctions de flèche ne doivent PAS être utilisées:

  1. Quand on veut une fonction de levage
    • comme les fonctions de flèche sont anonymes.
  2. Quand on veut utiliser this/arguments dans une fonction
    • comme les fonctions de flèche n'ont pas this/arguments, elles dépendent de leur contexte extérieur.
  3. Quand on veut utiliser la fonction nommée
    • comme les fonctions de flèche sont anonymes.
  4. Quand on veut utiliser la fonction comme constructor
    • comme les fonctions de flèche n'ont pas leur propre this.
  5. Lorsque nous voulons ajouter une fonction en tant que propriété dans un littéral d'objet et y utiliser un objet
    • comme nous ne pouvons pas accéder à this (qui devrait être objet lui-même).

Laissez-nous comprendre certaines des variantes des fonctions de flèche pour mieux comprendre:

Variante 1: Lorsque nous voulons passer plus d'un argument à une fonction et en retourner une valeur.

Version ES5 :

var multiply = function (a,b) {
    return a*b;
};
console.log(multiply(5,6)); //30

Version ES6 :

var multiplyArrow = (a,b) => a*b;
console.log(multiplyArrow(5,6)); //30

Remarque: le mot clé function n'est PAS requis. => est requis. {} sont facultatifs, lorsque nous ne fournissons pas {}return est ajouté implicitement par JavaScript et lorsque nous fournissons {}, nous devons ajouter return si nous en avons besoin. il.

Variante 2: Lorsque nous voulons transmettre UNIQUEMENT un argument à une fonction et en renvoyer une valeur.

Version ES5 :

var double = function(a) {
    return a*2;
};
console.log(double(2)); //4

Version ES6 :

var doubleArrow  = a => a*2;
console.log(doubleArrow(2)); //4

Remarque: Lorsque vous ne transmettez qu'un seul argument, vous pouvez omettre les parenthèses ().

Variante: Lorsque nous ne voulons PAS transmettre d'argument à une fonction et ne voulons PAS renvoyer de valeur.

Version ES5 :

var sayHello = function() {
    console.log("Hello");
};
sayHello(); //Hello

Version ES6 :

var sayHelloArrow = () => {console.log("sayHelloArrow");}
sayHelloArrow(); //sayHelloArrow

Variante 4: Lorsque nous voulons retourner explicitement à partir de fonctions fléchées.

Version ES6 :

var increment = x => {
  return x + 1;
};
console.log(increment(1)); //2

Variante 5: Lorsque nous voulons renvoyer un objet à partir de fonctions fléchées.

Version ES6 :

var returnObject = () => ({a:5});
console.log(returnObject());

Remarque: nous devons envelopper l'objet entre parenthèses () sinon JavaScript ne peut pas faire la différence entre un bloc et un objet.

Variante 6: Les fonctions de flèche ne possèdent PAS arguments (un tableau semblable à un objet), elles dépendent du contexte externe pour arguments.

Version ES6 :

function foo() {
  var abc = i => arguments[0];
  console.log(abc(1));
};    
foo(2); // 2

Remarque: foo est une fonction ES5, avec un tableau arguments semblable à un objet et un argument qui lui est transmis est 2 donc arguments[0] pour foo est 2.

abc est une fonction de flèche ES6 dans la mesure où elle n'a PAS sa propre fonction arguments, de sorte qu'elle affiche arguments[0] sur foo son contexte externe.

Variante 7: Les fonctions fléchées n'ont PAS this propres, elles dépendent du contexte externe pour this

Version ES5 :

var obj5 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
        setTimeout(function(){
        console.log(this.greet + ": " +  user); // "this" here is undefined.
        });
     }
};

obj5.greetUser("Katty"); //undefined: Katty

Remarque: le rappel transmis à setTimeout est une fonction ES5 et possède sa propre fonction this qui n'est pas définie dans l'environnement use-strict d'où nous obtenons une sortie:

undefined: Katty

Version ES6 :

var obj6 = {
  greet: "Hi, Welcome ",
  greetUser : function(user) {
    setTimeout(() => console.log(this.greet + ": " +  user)); 
      // this here refers to outer context
   }
};

obj6.greetUser("Katty"); //Hi, Welcome: Katty

Remarque: le rappel transmis à setTimeout est une fonction de flèche ES6 et il N'A PAS sa propre fonction this. Il est donc pris dans son contexte externe qui est greetUser qui a this. c'est-à-dire obj6 d'où nous obtenons la sortie:

Hi, Welcome: Katty

Divers: Nous ne pouvons pas utiliser new avec les fonctions de flèche. Les fonctions fléchées n'ont pas la propriété prototype. Nous n'avons PAS de liaison de this lorsque la fonction de flèche est appelée via apply ou call.

14
Manishz90

En plus des bonnes réponses apportées jusqu'à présent, j'aimerais présenter une raison très différente pour laquelle les fonctions de flèche sont fondamentalement meilleures que les fonctions JavaScript "ordinaires". Par souci de discussion, supposons temporairement que nous utilisons un vérificateur de type comme TypeScript ou le "Flow" de Facebook. Considérez le module suivant, qui contient du code ECMAScript 6 valide et des annotations de type Flow: (j'inclurai le code non typé, qui résulterait de Babel de manière réaliste, à la fin de cette réponse, pour qu'il puisse réellement être exécuté).

export class C {
  n : number;
  f1: number => number; 
  f2: number => number;

  constructor(){
    this.n = 42;
    this.f1 = (x:number) => x + this.n;
    this.f2 = function (x:number) { return  x + this.n;};
  }
}

Voyons maintenant ce qui se passe lorsque nous utilisons la classe C depuis un module différent, comme ceci:

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1: number = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2: number = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!

Comme vous pouvez le constater, le vérificateur de types a échoué ici: f2 était censé renvoyer un nombre, mais il a renvoyé une chaîne!

Pire, il semble que pas de vérificateur de type concevable peut gérer des fonctions JavaScript ordinaires (sans flèche), car le "this" de f2 ne figure pas dans la liste d'arguments de f2, donc le type requis pour " cela "ne pourrait éventuellement pas être ajouté comme une annotation à f2.

Ce problème concerne-t-il également les personnes qui n'utilisent pas de vérificateurs de type? Je pense que oui, car même quand nous n’avons pas de types statiques, nous pensons comme s’ils étaient là. ("Le premier paramètre doit être un nombre, le second une chaîne" etc.). Un "ce" argument masqué qui peut être utilisé ou non dans le corps de la fonction rend plus difficile notre comptabilité mentale.

Voici la version non typée exécutable, qui serait produite par Babel:

class C {
    constructor() {
        this.n = 42;
        this.f1 = x => x + this.n;
        this.f2 = function (x) { return x + this.n; };
    }
}

let o = { f1: new C().f1, f2: new C().f2, n: "foo" };
let n1 = o.f1(1); // n1 = 43
console.log(n1 === 43); // true
let n2 = o.f2(1); // n2 = "1foo"
console.log(n2 === "1foo"); // true, not a string!
6
Carsten Führmann

Je préfère utiliser les fonctions de flèche à tout moment où l'accès à la locale this n'est pas nécessaire, car la fonction de flèche ne lie pas leurs propres arguments this, arguments, super ou new.target .

3
zowers

Je maintiens toujours tout ce que j'ai écrit dans ma première réponse dans ce fil. Cependant, mon opinion sur le style de code a évolué depuis. J'ai donc une nouvelle réponse à cette question, qui s'appuie sur la dernière.

Concernant le lexical this

Dans ma dernière réponse, j'ai délibérément évité une croyance sous-jacente que je partage au sujet de cette langue, car elle n'était pas directement liée à l'argument que je défendais. Néanmoins, sans que cela soit explicitement énoncé, je peux comprendre pourquoi beaucoup de gens rechignent simplement à ma recommandation de ne pas utiliser de flèches, alors qu'ils trouvent les flèches si utiles.

Ma conviction est la suivante: nous ne devrions pas utiliser this en premier lieu. Par conséquent, si une personne évite délibérément d'utiliser this dans son code, la caractéristique "lexical this" des flèches n'a que peu ou pas de valeur. De plus, en partant du principe que this est une mauvaise chose, le traitement par Arrow de this est moins une "bonne chose"; il s’agit plutôt d’une forme de contrôle des dommages pour une autre mauvaise langue.

Je suppose que cela non plus n’arrive pas à certaines personnes, mais même à ceux à qui elles s'adressent, elles doivent toujours se trouver dans des bases de code où this apparaît cent fois par fichier et un peu (ou beaucoup) des dommages est tout ce qu'une personne raisonnable peut espérer. Donc, les flèches peuvent être bonnes, d’une certaine manière, quand elles améliorent une mauvaise situation.

Même s'il est plus facile d'écrire du code avec this avec des flèches que sans eux, les règles d'utilisation des flèches restent très complexes (voir: le thread actuel). Ainsi, les directives ne sont ni "claires" ni "cohérentes", comme vous l’avez demandé. Même si les programmeurs connaissent les ambiguïtés des flèches, je pense qu’ils haussent les épaules et les acceptent quand même, parce que la valeur de lexical this les éclipse.

Tout ceci est une préface à la réalisation suivante: si on n'utilise pas this, alors l'ambiguïté sur this que les flèches causent normalement devient sans importance. Les flèches deviennent plus neutres dans ce contexte.

Concernant la syntaxe abrégée

Lorsque j’ai écrit ma première réponse, j’étais d’avis que même l’adhésion servile aux meilleures pratiques était un prix intéressant à payer si cela signifiait que je pouvais produire un code plus parfait. Mais j’ai fini par me rendre compte que la concision pouvait également constituer une forme d’abstraction susceptible d’améliorer la qualité du code - suffisamment pour justifier parfois l’abandon des meilleures pratiques.

En d'autres termes: bon sang, je veux aussi des fonctions à une ligne!

Concernant une ligne directrice

Avec la possibilité de this- fonctions fléchées neutres, et la légèreté valant la peine d'être recherchée, je propose la directive suivante plus indulgente:

Guide pour la notation de fonction dans ES6:

  • N’utilisez pas this.
  • Utilisez les déclarations de fonction pour les fonctions que vous appelez par leur nom (car elles sont levées).
  • Utilisez les fonctions fléchées pour les rappels (car ils ont tendance à être agressifs).
2
Jackson

De manière simple,

var a =20; function a(){this.a=10; console.log(a);} 
//20, since the context here is window.

Un autre exemple:

var a = 20;
function ex(){
this.a = 10;
function inner(){
console.log(this.a); //can you guess the output of this line.
}
inner();
}
var test = new ex();

Réponse: La console afficherait 20.

La raison étant que chaque fois qu'une fonction est exécutée, sa propre pile est créée, dans cet exemple, la fonction ex est exécutée avec l'opérateur new afin qu'un contexte soit créé, et lorsque inner est exécuté, il JS créerait une nouvelle pile et exécuterait la fonction inner en tant que global context bien qu'il existe un contexte local.

Donc, si nous voulons que la fonction inner ait un contexte local qui est ex, nous devons lier le contexte à la fonction interne.

Les flèches résolvent ce problème, au lieu de prendre le Global context, elles prennent le local context s'il en existe. Dans le given example,, il prendra new ex() comme this.

Ainsi, dans tous les cas où la liaison est explicite, les flèches résolvent le problème par défaut.

1

Les fonctions fléchées ou Lambdas ont été introduites dans ES 6. Outre son élégance en syntaxe minimale, la différence fonctionnelle la plus notable concerne la portée de this dans une fonction de flèche

Dans les expressions de fonctions régulières , le mot clé this est lié à différentes valeurs en fonction du contexte dans lequel il est appelé.

Dans les fonctions de flèche , this est lexicalement ​​lié, ce qui signifie qu'il se ferme sur this à partir de la portée dans laquelle la fonction de flèche a été définie (parent-portée), et ne change pas peu importe où et comment il est appelé/appelé.

Limitations Arrow-Fonctions en tant que méthodes sur un objet

_// this = global Window
let objA = {
 id: 10,
 name: "Simar",
 print () { // same as print: function() 
  console.log(`[${this.id} -> ${this.name}]`);
 }
}
objA.print(); // logs: [10 -> Simar]
objA = {
 id: 10,
 name: "Simar",
 print: () => {
  // closes over this lexically (global Window)
  console.log(`[${this.id} -> ${this.name}]`);
 }
};
objA.print(); // logs: [undefined -> undefined]
_

Dans le cas de objA.print() lorsque la méthode print() a été définie à l'aide de la méthode function régulière, la résolution correcte de this en objA pour l'appel de la méthode a échoué lorsqu'elle a été définie comme une fonction flèche _=>_. C'est parce que this dans une fonction régulière lorsqu'elle est invoquée en tant que méthode sur un objet (objA), est l'objet lui-même. Toutefois, dans le cas d’une fonction de flèche, this est lié lexicalement à la this de la portée englobante dans laquelle il a été défini (global/Window dans notre cas) et reste identique lors de son invocation en tant que méthode sur objA.

Avantages des fonctions de flèche par rapport aux fonctions habituelles dans les méthodes d'un objet MAIS uniquement lorsque l'on s'attend à ce que this soit fixé et lié à la définition temporelle.

_/* this = global | Window (enclosing scope) */

let objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( function() {
    // invoked async, not bound to objB
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [undefined -> undefined]'
objB = {
 id: 20,
 name: "Paul",
 print () { // same as print: function() 
  setTimeout( () => {
    // closes over bind to this from objB.print()
    console.log(`[${this.id} -> ${this.name}]`);
  }, 1)
 }
};
objB.print(); // logs: [20 -> Paul]
_

Dans le cas de objB.print() où la méthode print() est définie en tant que fonction qui appelle _console.log(_ [$ {this.id} -> {this.name}] _)_ de manière asynchrone en tant que rappel sur setTimeout, this résolu correctement sur objB lorsqu'une fonction de flèche était utilisée comme rappel, mais échouait lorsque le rappel était défini comme fonction normale. C'est parce que la fonction flèche _=>_ est passée à setTimeout(()=>..) fermée sur this par lexicalement de son parent ie. invocation de objB.print() qui le définit. En d'autres termes, la fonction flèche _=>_ est passée à setTimeout(()==>... liée à objB en tant que this car l'invocation de objB.print()this était elle-même objB.

Nous pourrions facilement utiliser Function.prototype.bind(), pour que le rappel défini comme une fonction normale fonctionne, en le liant au correct this.

_const objB = {
 id: 20,
 name: "Singh",
 print () { // same as print: function() 
  setTimeout( (function() {
    console.log(`[${this.id} -> ${this.name}]`);
  }).bind(this), 1)
 }
}
objB.print() // logs: [20 -> Singh]
_

Cependant, les fonctions de flèche sont pratiques et moins sujettes aux erreurs dans le cas de rappels asynchrones où nous connaissons le this au moment de la définition de la fonction à laquelle il obtient et devrait être lié.

Limitation des flèches de fonctions où cela doit changer d'une invocation à l'autre

A tout moment, nous avons besoin d’une fonction dont this puisse être modifiée au moment de l’appel, nous ne pouvons pas utiliser de fonctions fléchées.

_/* this = global | Window (enclosing scope) */

function print() { 
   console.log(`[${this.id} -> {this.name}]`);
}
const obj1 = {
 id: 10,
 name: "Simar",
 print // same as print: print
};
obj.print(); // logs: [10 -> Simar]
const obj2 = {
 id: 20,
 name: "Paul",
};
printObj2 = obj2.bind(obj2);
printObj2(); // logs: [20 -> Paul]
print.call(obj2); // logs: [20 -> Paul]
_

Aucune de ces réponses ne fonctionnera avec la fonction de flèche const print = () => { console.log( [$ {this.id} -> {this.name}] _);}_ comme this ne peut pas être modifié et restera lié à la this de la portée englobante où elle a été défini (global/Window). Dans tous ces exemples, nous avons appelé la même fonction avec plusieurs objets (_obj1_ et _obj2_) l'un après l'autre, tous deux créés après la déclaration de la fonction print().

Celles-ci étaient des exemples artificiels, mais considérons d’autres exemples concrets. Si nous devions écrire notre méthode reduce() similaire à celle qui fonctionne sur arrays, nous ne pouvons à nouveau pas la définir en tant que lambda, car elle doit déduire this à partir du contexte d’appel, c.-à-d. le tableau sur lequel il a été appelé

Pour cette raison, les fonctions constructor ne peuvent jamais être définies comme des fonctions de flèche, car this pour une fonction constructeur ne peut pas être défini au moment de sa déclaration. Chaque fois qu'une fonction constructeur est invoquée avec le mot clé new, un nouvel objet est créé qui est ensuite lié à cet appel particulier.

De même, lorsque les structures ou les systèmes acceptent l’invocation ultérieure d’une ou plusieurs fonctions de rappel avec un contexte dynamique this, nous ne pouvons pas utiliser les fonctions fléchées, car this doit peut-être être modifié à chaque invocation. Cette situation se produit généralement avec les gestionnaires d'événements DOM

_'use strict'
var button = document.getElementById('button');
button.addEventListener('click', function {
  // web-api invokes with this bound to current-target in DOM
  this.classList.toggle('on');
});
var button = document.getElementById('button');
button.addEventListener('click', () => {
  // TypeError; 'use strict' -> no global this
  this.classList.toggle('on');
});
_

C’est aussi la raison pour laquelle dans des cadres tels que Angular 2 + et Vue.js Attendez-vous à ce que les méthodes de liaison composant-composant soient des méthodes/méthodes régulières, car this pour leur invocation est géré par les cadres des fonctions de liaison. (Angular utilise Zone.js pour gérer le contexte async pour les invocations de fonctions de liaison view-template).

D'autre part, dans React , lorsque nous voulons transmettre la méthode d'un composant en tant que gestionnaire d'événements, par exemple _<input onChange={this.handleOnchange} />_, nous devons définir handleOnchanage = (event)=> {this.props.onInputChange(event.target.value);} comme une fonction de flèche comme pour chaque appel, nous voulons que ce soit la même instance du composant qui a produit le JSX pour l’élément DOM rendu.


Cet article est également disponible sur ma Moyenne publication. Si vous aimez l'artile, ou si vous avez des commentaires et des suggestions, veuillez applaudir ou laisser ) commentaires sur Moyen .

1
Simar Singh