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:
new
this
lié à la portée lors de l'initialisationMis à 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:
this
et nous ne créons pas d’objet.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.
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:
function
dans la portée globale et pour les propriétés Object.prototype
.class
pour les constructeurs d'objet.=>
partout ailleurs.Pourquoi utiliser les fonctions de flèche presque partout?
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.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?
thisObject
.window
(portée globale) est mieux traité explicitement.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.function 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.
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
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.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,
this
lexicalementPar 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:
this
.this
.this
.EventTarget
avec this
.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:
function
.this
à une variable. N'utilisez pas () => {}
.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
À 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/
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:
this
/arguments
dans une fonction this
/arguments
, elles dépendent de leur contexte extérieur.constructor
this
.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
.
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!
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 .
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:
this
.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.
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 surthis
à 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é.
_// 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
.
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é.
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 .