Qu'est-ce qui est écrit ici? Dans les coulisses des objets JavaScript

JavaScript est un langage multi-paradigme qui prend en charge la programmation orientée objet et la liaison de méthode dynamique - un concept puissant qui permet à la structure du code JavaScript de changer pendant l'exécution du programme. Cela donne aux développeurs de sérieuses opportunités, cela rend le langage flexible, mais vous devez payer pour tout. Dans ce cas, vous devez payer avec l'intelligibilité du code. Une contribution significative à ce prix est apportée par le this , autour du comportement duquel beaucoup de choses ont été collectées qui peuvent dérouter le programmeur.



Liaison de méthode dynamique


La liaison dynamique vous permet de spécifier, lors de l'exécution du programme, et non lors de la compilation, la méthode qui doit être appelée lors de l'exécution d'une certaine commande. En JavaScript, ce mécanisme est implémenté à l'aide du this et de la chaîne de prototypes. En particulier, la valeur spécifique de this à l'intérieur de la méthode est déterminée lors de l'exécution et les règles de détermination de cette valeur varient en fonction de la façon dont la méthode a été déclarée.

Jouons un jeu. Je l'appelle "Qu'est-ce qui est écrit ici?". Voici sa première option - le code du module ES6:

 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) ]; 

Avant de continuer à lire, réfléchissez à ce qui se trouvera dans le tableau des réponses et notez les réponses. Après cela, testez-vous en answers tableau de answers aide de console.log() . Avez-vous réussi à «décrypter» correctement la valeur de this dans chacun des cas?

Nous analyserons ce problème, en commençant par le premier exemple. La construction obj.getThis() renvoie undefined . Pourquoi? Cette fonction de flèche ne peut pas être liée. Ces fonctions utilisent this partir de leur portée lexicale environnante. La méthode est appelée dans le module ES6, dans sa portée lexicale this ne sera pas undefined . Pour la même raison, undefined renverra undefined un appel à obj.getThis.call(a) . La valeur de this lorsque vous travaillez avec des fonctions fléchées ne peut pas être réaffectée même avec .call() ou .bind() . Cette valeur correspondra toujours à this partir de la portée lexicale, dans laquelle ces fonctions sont situées.

La commande obj.getThis2() montre comment travailler avec this lors de l'utilisation de méthodes d'objet normales. Si this pas lié à une méthode similaire, et à condition que cette méthode ne soit pas une fonction de flèche, c'est-à-dire qu'elle prend en charge this liaison, le mot clé this est lié à l'objet pour lequel la méthode est appelée en utilisant la syntaxe d'accès aux propriétés de l'objet via point ou en utilisant des crochets.

La construction obj.getThis2.call(a) est déjà un peu plus délicate à comprendre. La méthode call() vous permet d'appeler une fonction avec une valeur donnée de this , qui est indiquée comme argument optionnel. En d'autres termes, dans ce cas, this est extrait du paramètre .call() , par conséquent, l'appel à obj.getThis2.call(a) renvoie l'objet a .

Utilisation de la commande obj.getThis3 = obj.getThis.bind(obj); nous essayons de nous lier à this méthode, qui est une fonction de flèche. Comme nous l'avons déjà découvert, cela ne peut pas être fait. Par conséquent, les appels à obj.getThis3() et obj.getThis3.call(a) renvoient undefined .

Des méthodes qui sont des fonctions ordinaires peuvent être attachées à this , donc obj.getThis4() , comme prévu, renvoie obj . Un appel à obj.getThis4.call(a) renvoie obj et non, comme vous pouvez vous y attendre, a . Le fait est qu'avant d' obj.getThis4 = obj.getThis2.bind(obj); cette commande, nous l'avons déjà lié avec la commande obj.getThis4 = obj.getThis2.bind(obj); . Par conséquent, lors de l'exécution de obj.getThis4.call(a) , l'état de la méthode dans laquelle il se trouvait après la prise en compte de la première liaison est pris en compte.

Utiliser cela en classe


Voici la deuxième version de notre jeu - la même tâche, mais maintenant basée sur les classes. Ici, nous utilisons la syntaxe pour déclarer les champs de classe publique (pour le moment, la proposition de cette syntaxe est au troisième stade d'approbation, elle est disponible par défaut dans Chrome, vous pouvez l'utiliser avec @babel/plugin-proposal-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) ]; 

Avant de continuer à lire, pensez au code et notez votre vision de ce qui va tomber dans le tableau de answers2 .

Vous avez terminé?

Ici, tous les appels de méthode, à l'exception de obj2.getThis2.call(a) , obj2.getThis2.call(a) une référence à l'instance de l'objet. Le même appel renverra l'objet a . Les fonctions fléchées tirent toujours this de la portée lexicale. La différence entre cet exemple et le précédent est la différence dans la portée d'où this est tiré.

À savoir, nous travaillons ici avec les propriétés de classe, qui déterminent le comportement de ce code.

Le fait est que lors de la préparation du code pour l'exécution, les valeurs sont écrites dans les propriétés des classes comme ceci:

 class Obj { constructor() {   this.getThis = () => this; } ... 

En d'autres termes, il s'avère que la fonction flèche est déclarée dans le contexte de la fonction constructeur. Puisque nous travaillons avec une classe, la seule façon de l'instancier est d'utiliser le new mot-clé (si vous oubliez ce mot-clé, un message d'erreur sera affiché).

Les tâches les plus importantes résolues par le new mot clé sont de créer une nouvelle instance de l'objet et de la lier au constructeur. Cette fonctionnalité, compte tenu de ce dont nous avons déjà parlé dans la section précédente, devrait vous aider à comprendre ce qui se passe.

Résumé


Avez-vous terminé les tâches décrites dans cet article? Une bonne compréhension du comportement de this mot clé en JavaScript vous fera gagner une tonne de temps lors du débogage, lors de la recherche de raisons non évidentes d'erreurs obscures. Si vous avez répondu incorrectement à certaines des questions, cela signifie qu'il vous sera utile de vous entraîner.

Testez l'exemple de code, puis réessayez, etc., jusqu'à ce que vous puissiez répondre correctement à toutes les questions. Après avoir compris vous-même, trouvez quelqu'un prêt à vous écouter et dites-lui pourquoi les méthodes des tâches renvoient exactement ce qu'elles renvoient.

Si tout cela vous semble plus compliqué que prévu, sachez que vous n'êtes pas seul dans ce domaine. J'ai testé pas mal de développeurs pour connaître les fonctionnalités de this , et je pense qu'un seul d'entre eux était absolument précis dans toutes leurs réponses.

Ce sous-système du langage, qui au tout début ressemblait à une recherche dynamique de méthodes pouvant être influencées à l'aide de .call() , .bind() ou .apply() , a commencé à paraître beaucoup plus compliqué après l'apparition des fonctions fléchées et des classes.

Apparemment, il sera utile de noter les principales caractéristiques des classes et des fonctions fléchées en termes d'utilisation. N'oubliez pas que les fonctions fléchées utilisent toujours this partir de leur portée lexicale, et le this dans les classes est en fait lié à la fonction constructeur de la classe. Et si jamais vous sentez que vous ne savez pas exactement à quoi this renvoie, utilisez le débogueur pour vérifier vos hypothèses à ce sujet.

N'oubliez pas que vous pouvez faire beaucoup de choses en JavaScript sans utiliser this dans votre code. L'expérience me dit que presque tout code JS peut être réécrit sous la forme de fonctions pures qui acceptent tous les arguments avec lesquels il travaille, sous la forme d'une liste de paramètres explicitement spécifiée ( this peut être interprété comme un paramètre implicitement spécifié avec un état mutable). La logique contenue dans les fonctions pures est déterministe, ce qui améliore leur testabilité. De telles fonctions n'ont pas d'effets secondaires, ce qui signifie que lorsque vous travaillez avec elles, contrairement à la manipulation, il est peu probable que vous «cassiez» quoi que ce soit en dehors. Chaque fois que vous changez this , vous êtes confronté à un problème potentiel, à savoir que quelque chose qui en dépend peut cesser de fonctionner correctement.

Malgré ce qui précède, il convient de noter qu'il s'agit d'un concept utile. Par exemple, elle peut être appliquée afin d'organiser le partage d'une certaine méthode par une multitude d'objets. Même dans la programmation fonctionnelle, this peut être utile pour appeler d'autres méthodes d'un objet à partir d'une méthode, ce qui vous permet de créer quelque chose de nouveau basé sur des constructions existantes.



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


All Articles