Javascript : comprendre le mot clé this

Comprendre le mot clé this

Pour le premier article de cette nouvelle année, nous allons nous intéresser au fonctionnement du mot-clé this en JavaScript. Bien qu’il puisse paraître simple au premier abord, celui-ci se révèle bien plus complexe qu’il en a l’air notamment pour les développeurs venant d’autres langages.

Une histoire de contexte…

Dans la plupart des langages de programmation orientés objet tels que PHP , Java ou encore C++, this fait référence à l’instance actuelle d’une classe. En JavaScript, il ne désigne pas nécessairement l’objet courant, mais dépend plutôt du contexte dans lequel il est utilisé, ce qui est souvent source d’erreur et d’incompréhension. Faisons un peu le tour des différents types de contextes.

Le contexte global

Lorsque this est appelé en dehors de toute fonction, l’objet sur lequel celui-ci pointe est différent suivant l’environnement d’exécution.

Le navigateur web

Dans le cas d’une exécution dans le navigateur web, this fait référence à l’objet global window :

Node.js

Bien que Node.js possède un objet global nommé global , this ne fait pas référence à celui-ci. Node.js utilise le principe de module, le code que vous écrivez sera donc exécuté à l’intérieur d’un module. De ce fait, this fait référence à l’objet exports (ou module.exports qui est le même objet) :

Pour faire simple, lors de la création d’un module, Node.js encapsule votre code dans une fonction afin de ne pas polluer l’espace global (l’objet global ) et y injecte l’objet exports comme valeur pour this. Pour les plus téméraires d’entre vous, je vous invite à aller voir directement le code source de Node.js concernant les modules : module.js

Le contexte d’une fonction

JavaScript utilise en interne un mécanisme appelé “référence”. Ce mécanisme permet la résolution d’identifiant ou de propriété d’objet. Il utilise trois propriétés qui sont :

  • base : Cette propriété peut prendre plusieurs valeurs :
    • L’objet qui fait appel à un accesseur de propriété (pour faire simple, ce qui se trouve à gauche du ” .” ou ” []” );
    • L’objet global dans le cas contraire.
  • name : Représente l’identifiant ou le nom de la propriété de l’objet;
  • strict : Indique simplement si le mode strict est activé ou non.

Ainsi dans le cas d’un appel d’une fonction, une référence est créée et permet à JavaScript de connaître le nom de la fonction à appeler, il s’agit de la propriété name de la référence, mais également la valeur de this à l’intérieur de cette fonction puisqu’il s’agit simplement de la propriété base. Pour vraiment simplifier, vous pouvez imaginer que JavaScript remplace en interne, votre appel de fonction par :

Vous êtes sûrement déjà tombé sur l’erreur ReferenceError lors de l’appel à une fonction qui n’existe pas :

C’est tout simplement que la référence créée ne permet pas la résolution de l’identifiant notExistsFunction.

Par souci de simplicité et de clarté, nous représenterons, dans la suite de l’article, la référence sous la forme d’un objet littéral :

Voyons maintenant les différents cas d’appels de fonctions.

Les fonctions “ordinaires”

Prenons le cas d’une fonction “ordinaire” :

Lorsque nous appelons la fonction test, une “référence” est créée. Notre objet Reference ressemble donc à ceci :

La propriété name a donc pour valeur test. Le mode strict n’étant pas activé, la propriété strict a pour valeur false. Comme nous ne faisons pas appel à un accesseur de propriété lors de l’appel à la fonction test, la propriété base a pour valeur l’objet global qui dépend de l’environnement d’exécution.

Le navigateur web

Dans le cas d’une exécution dans le navigateur web, voici à quoi ressemble notre objet Reference :

La propriété base a donc pour valeur l’objet global window, de ce fait this fait référence à window :

Node.js

Dans le cas de Node.js, voici à quoi ressemble notre objet Reference :

la propriété base a donc pour valeur l’objet global global , de ce fait this fait également référence à global :

Mode strict

Lors de l’utilisation du mode strict, this est égal à undefined afin de prévenir tout risque de modification de l’objet global :

Les méthodes d’un objet

Lorsque nous appelons une méthode d’un objet, nous faisons appelle à un accesseur de propriété :

Ainsi lors de l’appel à la méthode test, une “référence” est créée et notre objet Reference ressemble à ceci :

Comme je l’ai dit précédemment, la propriété base et donc this, a pour valeur l’objet qui fait appel à un accesseur de propriété. Dans cet exemple, l’objet obj utilise l’accesseur de propriété “.“, this dans la fonction test, fait donc référence à cet objet. Par contre dans l’exemple suivant, this ne fait pas référence à l’objet obj, mais à l’objet global  :

On aurait pu s’attendre à ce que this fasse référence à l’objet obj, mais lors de l’affectation de la méthode test à la variable unbindTest, nous “brisons” le lien avec l’objet obj car nous ne faisons pas appel à un accesseur de propriété lors de l’appel à la fonction unbindTest. S’il y a une chose à retenir c’est que this fait référence à ce qui se trouve à gauche d’un accesseur de propriété que ce soit la notation point “.” ou crochets “[]“.

Le constructeur

La valeur de this, dans le cas d’un constructeur est très simple. Il correspond à l’instance de l’objet qui vient d’être créé lors de l’appel à l’opérateur new. Nous avons vu dans l’article sur l’héritage multiple comment fonctionnait cet opérateur. Pour rappel lors de l’appel à l’opérateur new :

  • Un nouvel objet est créé à partir de l’objet prototype du constructeur;
  • Le constructeur est appelé en lui fournissant comme valeur this l’objet nouvellement créé;
  • L’objet est retourné.

L’implémentation naïve de l’opérateur new donnerait ceci :

Je reviendrais plus en détail sur la fonction apply dans la suite de cet article.

Les fonctions fléchées

Nous avons vu que chaque fonction possède son propre this. Ce qui peut parfois poser quelques soucis si l’on ne fait pas attention :

Dans cet exemple, le premier this que nous rencontrons fait référence à l’objet obj. Jusqu’ici, il n’y a pas de soucis, c’est ce que l’on a vu précédemment. Par contre, dans la fonction de callback de setTimeout, this ne fait pas référence à l’objet obj, mais à l’objet global. Pourquoi ? Et bien c’est exactement le même cas que l’affectation d’une méthode d’un objet à une variable vu précédemment. Le lien qui existe avec l’objet est “brisé”. En effet, lors de l’appel de cette fonction, aucun accesseur de propriété n’est utilisé. Pour résoudre ce problème, nous devons “sauvegarder” la valeur de this comme ceci :

Avec ECMAScript 6, il est possible de résoudre simplement ce problème en utilisant les fonctions fléchées. Une fonction fléchée contrairement aux autres fonctions ne possède pas sa propre valeur this. La valeur de this à l’intérieur d’une fonction fléchée fait référence au this du parent. Voyons un exemple :

La valeur de this dans la fonction fléchée fait bien référence au this du parent à savoir celui qui se trouve dans la fonction test.

Injection de this

Avant de terminer cet article, nous allons voir qu’il est possible d’injecter la valeur de this lors de l’appel d’une fonction. Nous allons pour cela voir trois méthodes qui sont call, apply et bind.

call

La méthode call permet de faire appel à une fonction en lui fournissant comme paramètre la valeur de this ainsi que la liste des arguments de la fonction :

Voici un exemple :

Le premier paramètre de la méthode call est la valeur de this, suivi des arguments de la fonction. Ainsi lors de l’appel à la fonction add via la méthode call, la valeur de this fera référence à l’objet obj. Si nous appelons directement la fonction add une erreur se produit :

La valeur de this dans la fonction add fait référence cette fois-ci à l’objet global. Comme il ne possède pas de propriété values, une erreur se produit.

apply

La méthode apply fonctionne de la même façon que la méthode call à la différence que la liste des arguments n’est pas fournie individuellement, mais sous la forme d’un tableau :

Reprenons l’exemple précédent :

bind

Tout comme call et apply , bind permet d’injecter la valeur de this, mais contrairement aux deux autres méthodes qui appellent la fonction, bind en créer une nouvelle. La méthode bind possède la même signature que la méthode call :

Reprenons l’exemple précédemment :

Peu importe la façon dont sera appelée la fonction créée, la valeur de this à l’intérieur de celle-ci sera toujours celle fournie à la méthode bind .

Pour conclure

J’espère que vous comprenez maintenant le fonctionnement du mot-clé this et les différentes valeurs que celui-ci peut prendre suivant le contexte dans lequel il est utilisé. Pour résumer, il y a cinq choses que vous devez retenir afin de ne plus faire d’erreurs :

  • this fait référence à ce qui se trouve à gauche d’un accesseur de propriété que ce soit la notation point ” .” ou crochets ” []“;
  • Si aucun accesseur de propriété est utilisé, this fait référence à l’objet global;
  • Dans un constructeur (via l’appel à l’opérateur new), this fait référence à l’objet qui vient d’être créé;
  • Les fonctions fléchées ne possèdent pas de this;
  • Les méthodes call , apply et bind permettent d’injecter la valeur de this.

Si jamais vous avez des questions, n’hésitez surtout pas à les poser en commentaires.


Annonces partenaire

Je suis lead developer dans une boîte spécialisée dans l'univers du streaming/gaming, et en parallèle, je m'éclate en tant que freelance. Passionné par l'écosystème JavaScript, je suis un inconditionnel de Node.js depuis 2011. J'adore échanger sur les nouvelles tendances et partager mon expérience avec les autres développeurs. Si vous avez envie de papoter, n'hésitez pas à me retrouver sur Twitter, m'envoyer un petit email ou même laisser un commentaire.

3 commentaires

  1. Salut, super explications, je vais faire un tour sur les autres articles 😀
    J’ai juste une question, à propos du paragraphe sur les fonctions fléchées, le tout premier exemple :

    1 const obj = {
    2 test: function(delay) {
    3 console.log(this === obj); // true
    4 setTimeout(function() {
    5 console.log(this === obj); // false
    6 }, delay);
    7 }
    8 };
    9 obj.test(1000);

    Vous dites :

    J’ai testé pour me rendre compte du résultat, et je ne tombe pas sur l’objet global que j’aurais avec de simple fonction.
    J’ai un objet Timeout {
    _idleTimeout: 1,
    _idlePrev: null,
    …….
    [Symbol(refed)]: true,
    [Symbol(kHasPrimitive)]: false,
    [Symbol(asyncId)]: 5,
    [Symbol(triggerId)]: 1
    }
    Je me demandais pourquoi ? Est-ce la fonction setTimeout qui modifie le this de la fonction de callback ?
    Et puis la structure est légèrement différente, cet objet a un nom “Timeout”, ce qui n’est pas le cas de mes propres objets.

    Merci 🙂

    1. Je devine que tu as ce résultat sur Node. En fait, quand tu fais appelle à la fonction setTimeout sur Node, un objet Timeout est créé en interne. De ce fait le this appelé dans la fonction callback de setTimeout fait référence à l’instance de cet objet Timeout. Les fonctions de timers de Node, bien qu’elles aient le même nom, fonctionnent en interne différemment de celles du navigateur. D’ailleurs la fonction setTimeout coté navigateur est un “raccourci” de window.setTimeout là on voit bien le contexte global window donc le this est bien attaché à ce contexte global c’est-à-dire l’objet window

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.