
Photo: Curieuse Liliana Saeb (CC BY 2.0)
JavaScript est un langage multi-paradigme qui prend en charge la programmation orientée objet et la liaison dynamique. La liaison dynamique est un concept puissant qui vous permet de modifier la structure du code JavaScript au moment de l'exécution, mais cette puissance et cette flexibilité supplémentaires sont obtenues au prix d'une certaine confusion, dont la plupart est liée à this
comportement en JavaScript.
Liaison dynamique
Avec la liaison dynamique, la définition de la méthode à appeler se produit au moment de l'exécution et non au moment de la compilation. JavaScript le fait avec this
et la chaîne de prototypes. En particulier, à l'intérieur de la méthode, this
est déterminé lors de l'appel, et sa valeur sera différente selon la façon dont la méthode a été définie.
Jouons à un jeu. Je l'appelle "Qu'est- this
ici?"
const a = { a: 'a' }; const obj = { getThis: () => this, getThis2 () { return this; } }; obj.getThis3 = obj.getThis.bind(obj); obj.getThis4 = obj.getThis2.bind(obj); const answers = [ obj.getThis(), obj.getThis.call(a), obj.getThis2(), obj.getThis2.call(a), obj.getThis3(), obj.getThis3.call(a), obj.getThis4(), obj.getThis4.call(a) ];
Réfléchissez aux valeurs du tableau de answers
et vérifiez vos réponses avec console.log()
. Deviné?
Commençons par le premier cas et continuons dans l'ordre. obj.getThis()
renvoie undefined
, mais pourquoi? Les fonctions fléchées n'ont jamais this
. Au lieu de cela, ils prennent toujours this
de la portée lexicale ( environ lexical ceci ). Pour la racine du module ES6, la région lexicale aura une valeur undefined
de this
. obj.getThis.call(a)
pas non plus défini pour la même raison. Pour les fonctions fléchées, this
ne peut pas être remplacé, même avec .call()
ou .bind()
. this
sera toujours pris dans le domaine lexical.
obj.getThis2()
obtient la liaison lors de l'appel de méthode. S'il n'y avait pas cette liaison pour cette fonction auparavant, cela peut y être lié (car il ne s'agit pas d'une fonction de flèche). Dans ce cas, this
s'agit d'un objet obj
qui est lié au moment où la méthode est appelée avec .
ou la syntaxe d'accès à la propriété [squareBracket]
. ( noter la liaison implicite )
obj.getThis2.call(a)
peu plus compliqué. La méthode call()
appelle une fonction avec cette valeur donnée et des arguments facultatifs. En d'autres termes, la méthode obtient la liaison this
partir du paramètre obj.getThis2.call(a)
, donc obj.getThis2.call(a)
renvoie l'objet a
. ( noter la liaison explicite )
Dans le cas de obj.getThis3 = obj.getThis.bind(obj);
nous essayons d'obtenir une fonction de flèche avec this
attaché, qui, comme nous l'avons déjà compris, ne fonctionnera pas, donc nous ne sommes pas undefined
pour obj.getThis3()
et obj.getThis3.call(a)
respectivement.
Pour les méthodes régulières, vous pouvez lier, donc obj.getThis4()
renvoie obj
, comme prévu. Il a déjà obtenu sa liaison ici obj.getThis4 = obj.getThis2.bind(obj);
et obj.getThis4.call(a)
prend en compte la première liaison et renvoie obj
au lieu de a
.
Boule torsadée
Nous allons résoudre le même problème, mais cette fois, nous utilisons une class
avec des champs publics pour décrire l'objet (les innovations de Stage 3 au moment d'écrire ces lignes sont disponibles dans Chrome par défaut et avec @babel/plugin-offer-class-properties
):
class Obj { getThis = () => this getThis2 () { return this; } } const obj2 = new Obj(); obj2.getThis3 = obj2.getThis.bind(obj2); obj2.getThis4 = obj2.getThis2.bind(obj2); const answers2 = [ obj2.getThis(), obj2.getThis.call(a), obj2.getThis2(), obj2.getThis2.call(a), obj2.getThis3(), obj2.getThis3.call(a), obj2.getThis4(), obj2.getThis4.call(a) ];
Réfléchissez aux réponses avant de continuer.
Êtes-vous prêt?
Tous les appels sauf obj2.getThis2.call(a)
renvoient une instance de l'objet. obj2.getThis2.call(a)
renvoie a
. Les fonctions fléchées tirent toujours this
de leur environnement lexical. Il existe une différence dans la façon dont this
est défini à partir de l'environnement lexical pour les propriétés de classe. À l'intérieur, l'initialisation des propriétés de classe ressemble à ceci:
class Obj { constructor() { this.getThis = () => this; } ...
En d'autres termes, la fonction flèche est définie dans le contexte du constructeur. Comme il s'agit d'une classe, la seule façon de créer une instance est d'utiliser le new
mot-clé (en omettant new
entraînera une erreur). L'une des choses les plus importantes du new
mot clé est de créer une nouvelle instance de l'objet et de la lier à celle-ci dans le constructeur. Ce comportement, combiné avec d'autres comportements que nous avons mentionnés ci-dessus, devrait expliquer le reste.
Conclusion
Avez-vous réussi? Une bonne compréhension de la façon dont this
se comporte en JavaScript vous fera gagner beaucoup de temps lors du débogage de problèmes complexes. Si vous avez fait une erreur dans les réponses, cela signifie que vous devez vous entraîner un peu. Entraînez-vous avec les exemples, puis revenez en arrière et vérifiez à nouveau jusqu'à ce que vous puissiez exécuter le test et expliquer à quelqu'un d'autre pourquoi les méthodes renvoient ce qu'elles renvoient.
Si cela a été plus difficile que prévu, vous n'êtes pas seul. J'ai demandé à beaucoup de développeurs sur ce sujet et je pense que jusqu'à présent, un seul d'entre eux a fait face à cette tâche.
Ce qui a commencé comme une recherche de méthodes dynamiques que vous pouvez rediriger avec .call()
, .bind()
ou .apply()
est devenu beaucoup plus compliqué avec l'ajout de méthodes de classe et de fonctions flèches. Vous devriez peut-être à nouveau vous concentrer sur ce point. Rappelez-vous que les fonctions fléchées prennent toujours this
de la portée lexicale, et la class
est en fait limitée lexicalement par le constructeur de la classe sous le capot. Si vous en doutez, n'oubliez pas que vous pouvez utiliser un débogueur pour vérifier sa valeur.
N'oubliez pas qu'en résolvant de nombreuses tâches JavaScript, vous pouvez vous en passer. D'après mon expérience, presque tout peut être redéfini en termes de fonctions pures qui prennent tous les arguments utilisés comme paramètres explicites ( this
peut être considéré comme une variable implicite). La logique décrite par les fonctions pures est déterministe, ce qui la rend plus testable. De plus, avec cette approche, il n'y a pas d'effets secondaires, par conséquent, contrairement aux moments de manipulation, il est peu probable que vous cassiez quelque chose. Chaque fois que this
défini, quelque chose en fonction de sa valeur peut se casser.
Cependant, this
est parfois utile. Par exemple, pour échanger des méthodes entre un grand nombre d'objets. Même en programmation fonctionnelle, this
peut être utilisé pour accéder à d'autres méthodes d'objet afin d'implémenter les transformations algébriques nécessaires pour construire de nouvelles algèbres par-dessus celles existantes. Ainsi, .flatMap()
universel peut être obtenu en utilisant this.map()
et this.constructor.of()
.
Merci pour l'aide à la traduction de wksmirnowa et VIBaH_dev