Dans la plupart des langages de programmation, le mot-clé this est utilisé dans le code non-statique d’une classe pour se référer à l’objet courant.

Par exemple:

class Car {
    constructor() {
        this.wheels = 4;
    }
    addWheels(n) {
        this.wheels += n;
    }
}

let car = new Car();
car.addWheels(2);
console.log(car.wheels);

Dans cet exemple, pendant l’exécution du constructeur de la classe Car, la variable this a comme valeur l’objet qui est en train d’être construit.

Dans ce genre de contexte, il est très clair à quel objet si réfère la variable this dans le code des constructeurs et méthodes d’une classe. Mais dans la vraie vie, vous allez rencontrer des références à this dans d’autres contextes où son usage sera moins évident, et là où vous risquerez d’introduire des bugs en modifiant du code.

Dans cet article, nous verrons d’autres contextes où ce mot clé pourra être utilisé et sa signification.

Avant de commencer…

Il faut se rappeler que le Javascript n’est pas un langage orienté objet classique et que la plupart du temps c’est ce qui rend la compréhension du mot clé this difficile. Pour commencer, lorsque l’on lit du code Javascript il faut se rappeler qu’il n’y a que du code et des objets, tout les autres concepts ne sont que du sucre syntaxique!

Par exemple, on pourrait réécrire le code précédent comme:

function Car() {
    this.wheels = 4;
}

function addWheels(n) {
    this.wheels += n;
}

let car = {};
Car.call(car);
addWheels.call(car, 2);
console.log(car.wheels);

Ce code fait la même chose que le code précédent. Là on voit que la classe Car n’est qu’un ensemble de fonctions qui manipule un objet mystérieux dans une variable this. Un appel à fonction sur un objet construit à partir d’une classe n’est qu’un appel à la méthode call de la fonction (qui à l’exécution est un objet aussi). Le premier paramètre passé à la fonction call n’est que la valeur assumé par la variable this pendant l’exécution de la méthode. Le mot clé new ne fait qu’appeler la fonction constructeur sur un objet vide.

Le bon réflexe a avoir : dans quel contexte le code courant est appelé?

Quand on essaie de comprendre du code qui utilise le mot clé this, le bon réflexe est donc de regarder le contexte où le code qui utilise this a été appelé.

Dans un appel .call/apply

Comme on a montré dans l’exemple précédent, une fonction est un objet. On utilisera les fonctions call et apply pour définir directement la valeur de this à l’exécution.

Dans l’exemple suivant on affichera l’objet passé en paramètre dans la console.

function f()
{ 
    console.log(this);
} 
f.call({hello: 'world'});

Dans du code qui n’est pas dans une fonction

Il y a deux cas à considérer:

  • si on est en strict modethis vaut undefined;
  • si on en’est pas en strict mode, this est l’objet global en NodeJS ou l’objet window dans un navigateur.

Dans l’exemple suivant, la valeur affichée dans le console dépendra du  mode dans lequel s’exécute le code.

function f()
{
    console.log(this);
}
f();

Attention, cette règle est valable aussi pour une fonction définie à l’intérieur d’une autre fonction !

Dans l’exemple suivant, this n’est pas l’objet hello world, mais global, window ou undefined selon le mode d’exécution de ce code!

function f()
{ 
    function g() 
    { 
        console.log(this);
    }
    g();
}
f.call({hello: 'world'});

Dans un appel à new où à la notation `.` pour  accéder à un getter/setter ou à une méthode

Dans ce cas, Javascript se comporte comme prévu, et this  a la valeur de l’objet crée (pour un appel new) ou l’objet avant le `.` (pour les autres cas). Dans l’exemple suivant, on affichera un objet Car dans la console.

class Car {
    get f() {
        return this;
    }
}

let car = new Car();
console.log(car.f);

Dans une fonction fléchée

Dans ce cas, la valeur de this est figée à la valeur de this au moment de la création de la fonction.

Dans l’exemple suivant, lors du premier appel, on affichera helloWorld deux fois; lors du deuxième appel, on affichera l’objet global (car exécuté en NodeJS)  deux fois; lors du premier appel isolé, on affichera l’objet helloWorld, lord du deuxième, on affichera l’objet global.

let helloWorld = {hello: 'world'};
function f() {
    console.log('dans f, this vaut', this === global ? 'global' : this === helloWorld ? 'helloWorld' : '???');
    let p = () => console.log('dans p, this vaut', this === global ? 'global' : this === helloWorld ? 'helloWorld' : '???');
    p();
    return p;
}

console.log("premier appel");
q = f.apply(helloWorld);
console.log("appel isolé 1");
q();
console.log("deuxième appel");
q = f();
console.log("appel isolé 2");
q();
premier appel
dans f, this vaut helloWorld
dans p, this vaut helloWorld
appel isolé 1
dans p, this vaut helloWorld
deuxième appel
dans f, this vaut global
dans p, this vaut global
appel isolé 2
dans p, this vaut global

Comparons cet exemple avec l’exemple suivant, où on remplacera p par une fonction non fléchée.

let helloWorld = {hello: 'world'};
function f() {
    console.log('dans f, this vaut', this === global ? 'global' : this === helloWorld ? 'helloWorld' : '???');
    function p() { console.log('dans p, this vaut', this === global ? 'global' : this === helloWorld ? 'helloWorld' : '???'); }
    p();
    return p;
}

console.log("premier appel");
q = f.apply(helloWorld); 
console.log("appel isolé 1");
q(); 
console.log("deuxième appel");
q = f(); 
console.log("appel isolé 2");
q();

Dans ce cas, la variable this dans p vaudra toujours global, tandis que celle dans f aura une valeur différente dans chaque appel. Dans le premier appel, elle vaudra helloWorld, dans les autres, global.

premier appel
dans f, this vaut helloWorld
dans p, this vaut global
appel isolé 1
dans p, this vaut global
deuxième appel
dans f, this vaut global
dans p, this vaut global
appel isolé 2
dans p, this vaut global

Après un appel bind()

La méthode bind() d’une fonction sert à fixer la valeur de this pour tous les appels à cette fonction, indépendamment du contexte.

Dans le code suivant, on crée deux fonctions b et c, est bound à l’objet someObject tandis que b ne l’est pas.

let someObject = new String("object");
let otherObject = null;

b = function () {
    console.log("dans b, this vaut", this === someObject ? "someObject" : this === otherObject ? "otherObject" : '???');
}; 

c = function () {
    console.log("dans c, this vaut", this === someObject ? "someObject" : this === otherObject ? "otherObject" : '???');
}.bind(someObject);

b.apply(someObject);
c.apply(someObject);

otherObject = {
    b: b,
    c: c
};

otherObject.b();
otherObject.c();

On exécute les deux fonctions dans deux contextes: avec apply et avec la notation `.` pour appeler une méthode d’un objet.

La sortie après l’exécution de ce code est la suivante.

dans b, this vaut someObject
dans c, this vaut someObject
dans b, this vaut otherObject
dans c, this vaut someObject

On voit que pour cthis vaut toujours someObject, peu importe le contexte d’appel. Pour b, la valeur de dépends du contexte dans lequel la fonction est appelé.

Pour aller plus loin…

Cet article est encore trop court pour parler de tous les détails liés à l’utilisation du mot clé this, pour aller plus loin, je vous propose de vous référer à la documentation mozilla de ce mot clé pour avoir plus de détails et plus d’exemples.

Pour les adeptes de ES6, la documentation mozilla des fonctions fléchées peut aussi aider à comprendre les choix d’implémentation derrière le fonctionnement du mot clé this dans ces fonctions.

Je vous laisse trois articles intéressants sur le sujet:

Et pour finir, un livre partiellement dédié au sujet: