Qu'est-ce que c'est ici? Fonctionnement interne des objets JavaScript


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

Source: https://habr.com/ru/post/fr452192/


All Articles