Cours JavaScript et TypeScript · Module 2: Fonctions, tableaux et objets

Le mot-clé this en JavaScript

16 min de lecture
Dans cette leçon9

Bienvenue de retour à Jurassic Park ! Aujourd'hui, nous allons plonger en profondeur dans l'un des sujets les plus intrigants et parfois déroutants de JavaScript : le mot-clé this. Comprendre this est crucial pour une programmation orientée objet efficace en JavaScript et pour construire des systèmes complexes comme notre système de gestion du parc.

Qu'est-ce que this ?

Le mot-clé this en JavaScript désigne l'objet dans le contexte duquel une fonction est exécutée. Tu peux le voir comme le « lieu de travail actuel » - tout comme un employé de Jurassic Park peut travailler à différents endroits (le labo, l'enclos du T-Rex, le centre de contrôle), une fonction peut être exécutée dans différents contextes.

La valeur de this n'est pas déterminée au moment où la fonction est définie, mais au moment où elle est appelée, ce qui peut créer une certaine confusion. La valeur de this dépend de la façon dont la fonction est invoquée :

  1. Dans le contexte global
  2. Comme méthode d'objet
  3. Comme constructeur
  4. Avec les méthodes call(), apply() ou bind()
  5. Dans les fonctions fléchées

Examinons chacun de ces cas.

1. this dans le contexte global

Quand this est utilisé dans une fonction classique (qui n'est pas appelée comme méthode d'objet, hors mode strict), il désigne l'objet global : window dans le navigateur et global dans Node.js (globalThis est le nom commun aux deux). Au niveau supérieur d'un script dans le navigateur, this vaut aussi window, mais dans un fichier Node.js c'est module.exports, c'est-à-dire un objet vide {}, et dans les modules ES c'est undefined.

1// Dans la portée globale
2console.log(this); // window dans le navigateur, {} (module.exports) dans un fichier Node.js
3
4// Dans une fonction classique
5function checkGlobalThis() {
6  console.log(this); // aussi l'objet window/global
7}
8
9checkGlobalThis();

En mode strict ("use strict";), this dans une fonction classique vaudra undefined au lieu de l'objet global, ce qui aide à éviter la création accidentelle de variables globales.

1"use strict";
2
3function strictThis() {
4  console.log(this); // undefined
5}
6
7strictThis();

2. this comme méthode d'objet

Quand une fonction est appelée comme méthode d'un objet, this désigne cet objet. C'est l'un des usages les plus courants et les plus intuitifs de this en JavaScript.

1// Objet représentant un dinosaure
2const velociraptor = {
3  name: "Blue",
4  species: "Velociraptor",
5  age: 5,
6
7  // Méthode utilisant this
8  describe: function() {
9    console.log(`Ce ${this.species} s'appelle ${this.name} et a ${this.age} ans.`);
10  },
11
12  // Méthode qui modifie les données du dinosaure
13  ageDinosaur: function(years) {
14    this.age += years;
15    console.log(`${this.name} a maintenant ${this.age} ans.`);
16  }
17};
18
19// Appel des méthodes
20velociraptor.describe(); // "Ce Velociraptor s'appelle Blue et a 5 ans."
21velociraptor.ageDinosaur(2); // "Blue a maintenant 7 ans."

Dans l'exemple ci-dessus, this à l'intérieur des méthodes describe et ageDinosaur désigne l'objet velociraptor, ce qui permet aux méthodes d'accéder à ses propriétés.

Piège potentiel : la perte de contexte

Attention toutefois - le contexte this peut être perdu si tu assignes une méthode à une autre variable et que tu l'appelles comme une fonction classique :

1const velociraptor = {
2  name: "Blue",
3  describe: function() {
4    console.log(`Salut, je suis ${this.name} !`);
5  }
6};
7
8// Appel comme méthode - this désigne velociraptor
9velociraptor.describe(); // "Salut, je suis Blue !"
10
11// Mais quand on assigne la méthode à une variable...
12const describeDino = velociraptor.describe;
13
14// ...et qu'on l'appelle comme une fonction classique
15describeDino(); // "Salut, je suis undefined !"
16// (ou "Salut, je suis  !" s'il existe une variable globale name)

C'est un problème courant lorsqu'on passe des méthodes comme callbacks :

1const trex = {
2  name: "Rex",
3  roar: function() {
4    console.log(`${this.name} rugit fort !`);
5  }
6};
7
8// L'appel direct fonctionne
9trex.roar(); // "Rex rugit fort !"
10
11// Mais comme callback après 1 seconde...
12setTimeout(trex.roar, 1000); // this ne pointe pas vers trex, donc le nom Rex n'apparaîtra pas
13
14// Une solution (parmi d'autres) est d'utiliser une fonction fléchée ou bind() :
15setTimeout(() => trex.roar(), 1000); // "Rex rugit fort !"
16setTimeout(trex.roar.bind(trex), 1000); // "Rex rugit fort !"

3. this dans les constructeurs

Quand une fonction est appelée avec l'opérateur new (comme constructeur), this désigne l'objet nouvellement créé. C'est un mécanisme fondamental pour créer des instances d'objets en JavaScript.

1// Fonction constructeur pour les dinosaures
2function Dinosaur(name, species, age) {
3  // 'this' désigne l'objet nouvellement créé
4  this.name = name;
5  this.species = species;
6  this.age = age;
7
8  this.describe = function() {
9    console.log(`${this.name} est un ${this.species} âgé de ${this.age} ans.`);
10  };
11}
12
13// Création de nouvelles instances
14const blue = new Dinosaur("Blue", "Velociraptor", 5);
15const rex = new Dinosaur("Rex", "Tyrannosaurus", 8);
16
17blue.describe(); // "Blue est un Velociraptor âgé de 5 ans."
18rex.describe(); // "Rex est un Tyrannosaurus âgé de 8 ans."
19
20// Modifions l'âge de Blue
21blue.age = 6;
22blue.describe(); // "Blue est un Velociraptor âgé de 6 ans."

Dans le JavaScript moderne (ES6+), au lieu des fonctions constructeur, on utilise souvent des classes, qui offrent une syntaxe plus propre pour faire la même chose :

1// Classe ES6 pour les dinosaures
2class Dinosaur {
3  constructor(name, species, age) {
4    this.name = name;
5    this.species = species;
6    this.age = age;
7  }
8
9  // Les méthodes sont automatiquement ajoutées au prototype
10  describe() {
11    console.log(`${this.name} est un ${this.species} âgé de ${this.age} ans.`);
12  }
13
14  // Méthode qui change l'âge
15  growOlder(years = 1) {
16    this.age += years;
17    console.log(`${this.name} a maintenant ${this.age} ans.`);
18  }
19}
20
21// Création d'instances
22const trixie = new Dinosaur("Trixie", "Triceratops", 7);
23trixie.describe(); // "Trixie est un Triceratops âgé de 7 ans."
24trixie.growOlder(3); // "Trixie a maintenant 10 ans."

4. Contrôler this : call(), apply() et bind()

JavaScript offre trois méthodes qui permettent de contrôler explicitement la valeur de this lors de l'appel d'une fonction :

call()

La méthode call() appelle une fonction avec une valeur this précise et des arguments passés individuellement :

1function examineSpecimen(testType, labNumber) {
2  console.log(`Examen du ${this.species} nommé ${this.name}...`);
3  console.log(`Type de test : ${testType}, Laboratoire : ${labNumber}`);
4}
5
6const specimen = {
7  name: "Echo",
8  species: "Velociraptor",
9  age: 3
10};
11
12// Appel de la fonction avec 'this' défini sur specimen
13examineSpecimen.call(specimen, "ADN", "Lab-A");
14// "Examen du Velociraptor nommé Echo..."
15// "Type de test : ADN, Laboratoire : Lab-A"

apply()

La méthode apply() fonctionne comme call(), mais les arguments sont passés sous forme de tableau :

1// Même fonction
2function examineSpecimen(testType, labNumber) {
3  console.log(`Examen du ${this.species} nommé ${this.name}...`);
4  console.log(`Type de test : ${testType}, Laboratoire : ${labNumber}`);
5}
6
7// Appel avec les arguments dans un tableau
8examineSpecimen.apply(specimen, ["Comportemental", "Lab-B"]);
9// "Examen du Velociraptor nommé Echo..."
10// "Type de test : Comportemental, Laboratoire : Lab-B"

bind()

La méthode bind() crée une nouvelle fonction avec une valeur this assignée de façon permanente :

1const dinosaur = {
2  name: "Delta",
3  species: "Velociraptor"
4};
5
6function makeSound(sound) {
7  console.log(`${this.name} émet un son : ${sound} !`);
8}
9
10// Création d'une nouvelle fonction avec 'this' lié à dinosaur
11const dinosaurSound = makeSound.bind(dinosaur);
12
13// On peut maintenant appeler cette fonction plusieurs fois
14dinosaurSound("Grognement"); // "Delta émet un son : Grognement !"
15dinosaurSound("Sifflement"); // "Delta émet un son : Sifflement !"
16
17// On peut aussi pré-lier des arguments
18const deltaRoar = makeSound.bind(dinosaur, "ROAR");
19deltaRoar(); // "Delta émet un son : ROAR !"

Exemple pratique : système de suivi des dinosaures

L'exemple suivant montre comment différentes méthodes de contrôle de this peuvent servir à créer un système de surveillance flexible :

1const dinoMonitoringSystem = {
2  activeDinosaurs: [],
3
4  // Enregistrer un nouveau dinosaure
5  registerDinosaur: function(dinosaur) {
6    this.activeDinosaurs.push(dinosaur);
7    console.log(`Nouveau dinosaure enregistré : ${dinosaur.name} (${dinosaur.species})`);
8  },
9
10  // Trouver un dinosaure par son nom
11  findDinosaurByName: function(name) {
12    return this.activeDinosaurs.find(dino => dino.name === name);
13  },
14
15  // Déclencher une procédure d'alerte
16  triggerAlert: function(message) {
17    console.log(`ALERTE SYSTÈME : ${message}`);
18
19    // Notifier au sujet de tous les dinosaures actifs
20    this.activeDinosaurs.forEach(function(dino) {
21      console.log(`- ${dino.name} (${dino.species}) dans le secteur ${dino.sector}`);
22    });
23  }
24};
25
26// Classe Dinosaur avec une méthode de déplacement
27class Dinosaur {
28  constructor(name, species, sector) {
29    this.name = name;
30    this.species = species;
31    this.sector = sector;
32    this.status = "calme";
33  }
34
35  move(newSector, monitoringSystem) {
36    // On peut passer monitoringSystem en paramètre
37    console.log(`${this.name} se déplace du secteur ${this.sector} au secteur ${newSector}`);
38    this.sector = newSector;
39
40    // Ou utiliser call() pour invoquer une méthode avec le bon contexte
41    const alertMessage = `Le dinosaure ${this.name} est entré dans le secteur ${newSector} !`;
42    monitoringSystem.triggerAlert.call(monitoringSystem, alertMessage);
43  }
44
45  // Méthode qui change le statut du dinosaure
46  updateStatus(newStatus) {
47    this.status = newStatus;
48    console.log(`Statut du dinosaure ${this.name} changé en : ${this.status}`);
49    return this;
50  }
51}
52
53// Initialiser le système et les dinosaures
54const rex = new Dinosaur("Rex", "Tyrannosaurus", "A");
55const blue = new Dinosaur("Blue", "Velociraptor", "B");
56const delta = new Dinosaur("Delta", "Velociraptor", "B");
57
58// Enregistrer les dinosaures dans le système
59dinoMonitoringSystem.registerDinosaur(rex);
60dinoMonitoringSystem.registerDinosaur(blue);
61dinoMonitoringSystem.registerDinosaur(delta);
62
63// Déplacer un dinosaure
64blue.move("C", dinoMonitoringSystem);
65
66// Chaînage de méthodes (grâce à return this)
67rex.updateStatus("agité").updateStatus("agressif");
68
69// Utilisation de bind() pour créer une fonction avec un 'this' prédéfini
70const checkRexStatus = function() {
71  console.log(`Statut du T-Rex : ${this.status}`);
72};
73
74const boundCheckStatus = checkRexStatus.bind(rex);
75boundCheckStatus(); // "Statut du T-Rex : agressif"

5. this dans les fonctions fléchées

Les fonctions fléchées, introduites en ES6, gèrent this différemment des fonctions classiques. Elles n'ont pas leur propre this ; à la place, elles « héritent » de this de la portée lexicale environnante.

1const parkSystem = {
2  name: "Jurassic Park",
3  dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
4
5  // Méthode utilisant une fonction fléchée
6  listDinosaurs: function() {
7    console.log(`${this.name} contient les dinosaures suivants :`);
8
9    // La fonction fléchée préserve 'this' de la portée de listDinosaurs
10    this.dinosaurs.forEach(dino => {
11      console.log(`- ${dino} dans le parc ${this.name}`);
12    });
13
14    // Pour comparaison - une fonction classique perdrait le contexte 'this'
15    /*
16    this.dinosaurs.forEach(function(dino) {
17      // 'this' désigne l'objet global ou vaut undefined en mode strict
18      console.log(`- ${dino} dans le parc ${this.name}`); // this.name serait undefined
19    });
20    */
21  }
22};
23
24parkSystem.listDinosaurs();
25// "Jurassic Park contient les dinosaures suivants :"
26// "- T-Rex dans le parc Jurassic Park"
27// "- Velociraptor dans le parc Jurassic Park"
28// "- Triceratops dans le parc Jurassic Park"

Les fonctions fléchées sont particulièrement utiles quand tu veux préserver le contexte this dans des fonctions imbriquées, des callbacks ou de la programmation asynchrone.

Exemple pratique : système de surveillance avec minuteries

1class DinosaurMonitor {
2  constructor(name, species) {
3    this.name = name;
4    this.species = species;
5    this.status = "Normal";
6    this.lastUpdated = new Date();
7    this.checkInterval = null;
8  }
9
10  // Démarrer la surveillance avec des vérifications de statut régulières
11  startMonitoring() {
12    console.log(`Démarrage de la surveillance du dinosaure : ${this.name}`);
13
14    // La fonction fléchée préserve le contexte 'this'
15    this.checkInterval = setInterval(() => {
16      this.checkStatus();
17    }, 5000);
18
19    // Si on utilisait une fonction classique, on perdrait le contexte :
20    /*
21    this.checkInterval = setInterval(function() {
22      // ici 'this' désigne l'objet global, pas l'instance de DinosaurMonitor
23      this.checkStatus(); // Erreur : this.checkStatus is not a function
24    }, 5000);
25    */
26  }
27
28  // Vérifier le statut du dinosaure
29  checkStatus() {
30    const now = new Date();
31    const timeSinceLastUpdate = (now - this.lastUpdated) / 1000; // en secondes
32
33    console.log(`Vérification du statut du dinosaure ${this.name} (${this.species})...`);
34    console.log(`Dernière mise à jour : il y a ${timeSinceLastUpdate.toFixed(1)} secondes`);
35    console.log(`Statut : ${this.status}`);
36
37    this.lastUpdated = now;
38
39    // Simulation d'un changement de statut (dans un vrai système, on aurait ici les données des capteurs)
40    if (Math.random() < 0.3) {
41      const possibleStatuses = ["Normal", "Agité", "Au repos", "Se nourrit", "Agressif"];
42      this.status = possibleStatuses[Math.floor(Math.random() * possibleStatuses.length)];
43      console.log(`Nouveau statut : ${this.status}`);
44    }
45  }
46
47  // Arrêter la surveillance
48  stopMonitoring() {
49    if (this.checkInterval) {
50      clearInterval(this.checkInterval);
51      this.checkInterval = null;
52      console.log(`Surveillance du dinosaure arrêtée : ${this.name}`);
53    }
54  }
55}
56
57// Créer et démarrer un moniteur
58const blueMonitor = new DinosaurMonitor("Blue", "Velociraptor");
59blueMonitor.startMonitoring();
60
61// Arrêter le moniteur après 20 secondes
62setTimeout(() => {
63  blueMonitor.stopMonitoring();
64}, 20000);

Les pièges courants avec this

Le mot-clé this peut être source de nombreux bugs en JavaScript. Voici quelques pièges courants et comment les éviter :

1. Perdre le contexte dans les fonctions imbriquées

1const dinosaur = {
2  name: "Rex",
3  behaviors: ["chasser", "se reposer", "courir"],
4
5  logBehaviors: function() {
6    // ici 'this' désigne 'dinosaur'
7    console.log(`Le dinosaure ${this.name} présente les comportements suivants :`);
8
9    // Problème : 'this' dans la fonction forEach désigne l'objet global
10    this.behaviors.forEach(function(behavior) {
11      // 'this.name' est undefined
12      console.log(`- ${this.name} aime ${behavior}`);
13    });
14  }
15};
16
17dinosaur.logBehaviors();
18// "Le dinosaure Rex présente les comportements suivants :"
19// "- undefined aime chasser"
20// "- undefined aime se reposer"
21// "- undefined aime courir"

Solutions :

  1. Utiliser une fonction fléchée :
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["chasser", "se reposer", "courir"],
4
5  logBehaviors: function() {
6    console.log(`Le dinosaure ${this.name} présente les comportements suivants :`);
7
8    // La fonction fléchée préserve 'this' de la portée environnante
9    this.behaviors.forEach(behavior => {
10      console.log(`- ${this.name} aime ${behavior}`);
11    });
12  }
13};
  1. Stocker une référence à this dans une variable :
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["chasser", "se reposer", "courir"],
4
5  logBehaviors: function() {
6    console.log(`Le dinosaure ${this.name} présente les comportements suivants :`);
7
8    // Stocke la référence à 'this' dans une variable 'self' ou 'that'
9    const self = this;
10    this.behaviors.forEach(function(behavior) {
11      console.log(`- ${self.name} aime ${behavior}`);
12    });
13  }
14};
  1. Utiliser la méthode bind() :
1const dinosaur = {
2  name: "Rex",
3  behaviors: ["chasser", "se reposer", "courir"],
4
5  logBehaviors: function() {
6    console.log(`Le dinosaure ${this.name} présente les comportements suivants :`);
7
8    // Utilisation de bind() pour attacher 'this'
9    this.behaviors.forEach(function(behavior) {
10      console.log(`- ${this.name} aime ${behavior}`);
11    }.bind(this));
12  }
13};

2. Les méthodes comme callbacks

Quand une méthode d'objet est passée comme callback, elle perd son contexte this :

1class DinosaurTracker {
2  constructor(name) {
3    this.name = name;
4    this.tracked = false;
5  }
6
7  startTracking() {
8    this.tracked = true;
9    console.log(`Suivi du dinosaure ${this.name} démarré`);
10  }
11}
12
13const blueTracker = new DinosaurTracker("Blue");
14
15// Fonctionne correctement
16blueTracker.startTracking();
17
18// Ne fonctionne pas - 'this' ne désigne pas blueTracker
19setTimeout(blueTracker.startTracking, 1000); // this ne pointe pas vers blueTracker

Solutions :

  1. Utiliser une fonction fléchée :
1setTimeout(() => blueTracker.startTracking(), 1000);
  1. Utiliser bind() :
1setTimeout(blueTracker.startTracking.bind(blueTracker), 1000);

Exemple avancé : système de gestion du parc

Voici un exemple plus complexe d'un système de gestion de Jurassic Park qui utilise différents aspects du mot-clé this :

1// Système principal de gestion du parc
2const parkManagementSystem = {
3  parkName: "Jurassic Park",
4  status: "opérationnel",
5  securityLevel: "normal",
6
7  // Sous-systèmes du parc
8  subsystems: {
9    power: { status: "en ligne", output: "100%" },
10    security: { status: "active", fences: "opérationnelles" },
11    visitorCenter: { status: "ouvert", visitors: 120 }
12  },
13
14  // Méthode d'initialisation du système
15  initialize: function() {
16    console.log(`Initialisation du système de gestion du parc : ${this.parkName}`);
17
18    // Vérifier tous les sous-systèmes
19    this.checkAllSubsystems();
20
21    // Mettre en place la surveillance régulière
22    this.startMonitoring();
23
24    return this;
25  },
26
27  // Vérifier le statut des sous-systèmes
28  checkAllSubsystems: function() {
29    console.log("Vérification du statut de tous les sous-systèmes...");
30
31    // Utilisation de Object.entries et d'une fonction fléchée pour préserver le contexte 'this'
32    Object.entries(this.subsystems).forEach(([name, system]) => {
33      console.log(`- Sous-système ${name} : ${system.status}`);
34
35      // Appeler les vérifications spécifiques de chaque sous-système
36      this[`check${name.charAt(0).toUpperCase() + name.slice(1)}`]();
37    });
38  },
39
40  // Méthodes de vérification spécifiques à chaque sous-système
41  checkPower: function() {
42    const powerSystem = this.subsystems.power;
43    console.log(`  Vérification de l'alimentation : ${powerSystem.status}, Sortie : ${powerSystem.output}`);
44  },
45
46  checkSecurity: function() {
47    const securitySystem = this.subsystems.security;
48    console.log(`  Vérification de la sécurité : ${securitySystem.status}, Clôtures : ${securitySystem.fences}`);
49  },
50
51  checkVisitorCenter: function() {
52    const visitorSystem = this.subsystems.visitorCenter;
53    console.log(`  Vérification du centre des visiteurs : ${visitorSystem.status}, Visiteurs : ${visitorSystem.visitors}`);
54  },
55
56  // Démarrer la surveillance régulière
57  startMonitoring: function() {
58    console.log("Démarrage de la surveillance régulière du parc...");
59
60    // Fonction fléchée pour préserver le contexte 'this'
61    this.monitoringInterval = setInterval(() => {
62      this.checkSecurityStatus();
63    }, 10000);
64
65    // Simuler un appel de méthode après 5 secondes
66    setTimeout(() => this.simulateSecurityAlert(), 5000);
67  },
68
69  // Vérifier l'état de sécurité
70  checkSecurityStatus: function() {
71    console.log(`[SURVEILLANCE] Statut du parc : ${this.status}, Niveau de sécurité : ${this.securityLevel}`);
72  },
73
74  // Simuler une alerte de sécurité
75  simulateSecurityAlert: function() {
76    console.log("SIMULATION : Brèche de sécurité détectée dans le secteur B !");
77
78    // Changer le statut du parc et le niveau de sécurité
79    this.status = "alerte";
80    this.securityLevel = "élevé";
81    console.log(`Statut du parc changé en : ${this.status}`);
82    console.log(`Niveau de sécurité relevé à : ${this.securityLevel}`);
83
84    // Vérification directe du statut
85    this.checkSecurityStatus();
86
87    // Créer une équipe d'intervention d'urgence à l'aide d'un constructeur
88    const responseTeam = new EmergencyResponseTeam(this);
89
90    // Passer une méthode comme callback tout en préservant le contexte
91    responseTeam.respondToAlert(this.resolveSecurityAlert.bind(this));
92  },
93
94  // Résoudre l'alerte de sécurité
95  resolveSecurityAlert: function(report) {
96    console.log(`Rapport reçu de l'équipe d'intervention : ${report}`);
97
98    // Rétablir le statut normal
99    this.status = "opérationnel";
100    this.securityLevel = "normal";
101    console.log("Alerte de sécurité résolue.");
102    console.log(`Statut du parc rétabli à : ${this.status}`);
103
104    // Vérifier le statut actuel
105    this.checkSecurityStatus();
106  },
107
108  // Arrêter la surveillance
109  stopMonitoring: function() {
110    if (this.monitoringInterval) {
111      clearInterval(this.monitoringInterval);
112      console.log("Surveillance du parc arrêtée.");
113    }
114  }
115};
116
117// Constructeur pour l'équipe d'intervention d'urgence
118function EmergencyResponseTeam(park) {
119  // 'this' désigne le nouvel objet
120  this.teamName = "Équipe d'intervention d'urgence";
121  this.members = ["Owen Grady", "Claire Dearing", "Dr. Wu"];
122  this.assignedPark = park;
123
124  this.respondToAlert = function(callback) {
125    console.log(`${this.teamName} intervient sur une alerte au parc : ${this.assignedPark.parkName}`);
126
127    // Simuler le temps de réponse
128    setTimeout(() => {
129      console.log(`${this.teamName} a sécurisé la zone et rétabli les clôtures.`);
130
131      // Appeler le callback avec un rapport
132      if (typeof callback === 'function') {
133        callback("Menace neutralisée, clôtures réparées");
134      }
135    }, 3000);
136  };
137}
138
139// Initialiser et démarrer le système
140parkManagementSystem.initialize();
141
142// Arrêter le système après 20 secondes
143setTimeout(() => {
144  parkManagementSystem.stopMonitoring();
145}, 20000);

Récapitulatif et bonnes pratiques

Le mot-clé this est un outil puissant en JavaScript, mais il demande de comprendre comment il se comporte dans différents contextes :

  1. Dans le contexte global : this vaut window dans le navigateur et module.exports ({}) dans un fichier Node.js ; dans une fonction classique hors mode strict, c'est l'objet global (globalThis).

  2. Dans une méthode d'objet : this désigne l'objet qui a appelé la méthode.

  3. Dans un constructeur : this désigne l'objet nouvellement créé.

  4. Dans les fonctions fléchées : this est hérité de la portée lexicale environnante.

  5. Avec call/apply/bind : this est explicitement spécifié par le programmeur.

Bonnes pratiques :

  1. Utilise les fonctions fléchées pour les callbacks et les fonctions imbriquées afin de préserver le contexte this.

  2. Utilise les méthodes call(), apply() ou bind() quand tu as besoin de contrôler explicitement la valeur de this.

  3. Évite de créer des méthodes dans les constructeurs ; ajoute-les plutôt au prototype pour éviter la duplication de code.

  4. Souviens-toi que this est déterminé au moment de l'appel de la fonction, pas au moment de sa définition.

  5. Utilise les classes ES6 au lieu des constructeurs traditionnels pour un code plus propre.

Dans la prochaine leçon, nous explorerons plus en profondeur les tableaux en JavaScript, qui sont un élément clé de la gestion des collections de données dans notre Jurassic Park.

Code de cette leçon : index.js
1// Mot-clé 'this' - Parc Jurassique
2console.log("Laboratoire du 'this' en JavaScript");
3console.log("Comprendre le contexte d'exécution\n");
4
5// ===========================================
6// 1. LES BASES DE 'this'
7// ===========================================
8console.log("=== 1. Qu'est-ce que 'this' ? ===");
9
10// 'this' fait référence à l'objet, dans le contexte qui exécute le code
11
12const dinosaur = {
13  name: "Tyrannosaurus Rex",
14  age: 8,
15
16  roar: function() {
17    console.log(`\n${this.name} rugit: ROAARRR!`);
18    console.log(`   Âge: ${this.age} ans`);
19  }
20};
21
22dinosaur.roar(); // 'this' pointe vers l'objet 'dinosaur'
23
24// ===========================================
25// 2. 'this' DANS DIFFÉRENTS CONTEXTES
26// ===========================================
27console.log("\n=== 2. Contextes de 'this' ===");
28
29// Contexte 1: Méthode d'objet
30const raptor = {
31  name: "Velociraptor",
32  speed: 40,
33
34  run: function() {
35    console.log(`\n${this.name} court à une vitesse de ${this.speed} km/h`);
36  }
37};
38
39raptor.run(); // this = raptor
40
41// Contexte 2: Fonction globale (strict mode)
42function globalFunction() {
43  console.log("\nFonction globale - this:", typeof this);
44  // En strict mode: undefined
45  // Sans strict mode: window (navigateur) ou global (Node.js)
46}
47
48globalFunction();
49
50// Contexte 3: Constructeur
51function Dinosaur(name, species) {
52  this.name = name;
53  this.species = species;
54  this.roar = function() {
55    console.log(`${this.name} (${this.species}): ROAARRR!`);
56  };
57}
58
59const trex = new Dinosaur("Rexy", "T-Rex");
60console.log("\nConstructeur:");
61console.log(`   Nom: ${trex.name}`);
62console.log(`   Espèce: ${trex.species}`);
63trex.roar();
64
65// ===========================================
66// 3. PERTE DU CONTEXTE 'this'
67// ===========================================
68console.log("\n=== 3. Perte du contexte 'this' ===");
69
70const park = {
71  name: "Jurassic Park",
72  dinosaurs: ["T-Rex", "Velociraptor", "Triceratops"],
73
74  displayDinosaurs: function() {
75    console.log(`\nDinosaures dans ${this.name}:`);
76
77    // PROBLÈME: forEach avec une fonction traditionnelle
78    // this.dinosaurs.forEach(function(dino) {
79    //   console.log(`   - ${dino} de ${this.name}`); // this.name = undefined!
80    // });
81
82    // SOLUTION 1: Fonction fléchée
83    this.dinosaurs.forEach(dino => {
84      console.log(`   - ${dino} de ${this.name}`);
85    });
86  }
87};
88
89park.displayDinosaurs();
90
91// SOLUTION 2: bind()
92const park2 = {
93  name: "Isla Nublar",
94  dinosaurs: ["Stegosaurus", "Brachiosaurus"],
95
96  displayDinosaurs: function() {
97    console.log(`\nDinosaures dans ${this.name}:`);
98
99    this.dinosaurs.forEach(function(dino) {
100      console.log(`   - ${dino} de ${this.name}`);
101    }.bind(this)); // Liaison de this
102  }
103};
104
105park2.displayDinosaurs();
106
107// ===========================================
108// 4. MÉTHODES: call(), apply(), bind()
109// ===========================================
110console.log("\n=== 4. Méthodes: call(), apply(), bind() ===");
111
112const dinoInfo = {
113  describe: function(habitat, diet) {
114    console.log(`\n${this.name}:`);
115    console.log(`   Environnement: ${habitat}`);
116    console.log(`   Régime: ${diet}`);
117  }
118};
119
120const dino1 = { name: "T-Rex" };
121const dino2 = { name: "Triceratops" };
122
123// call() - appel avec un this explicite et des arguments
124dinoInfo.describe.call(dino1, "Forêt tropicale", "Carnivore");
125
126// apply() - comme call(), mais avec les arguments dans un tableau
127dinoInfo.describe.apply(dino2, ["Savane", "Herbivore"]);
128
129// bind() - crée une nouvelle fonction avec un this lié
130const describeTRex = dinoInfo.describe.bind(dino1);
131describeTRex("Jungle", "Carnivore");
132
133// ===========================================
134// 5. FONCTIONS FLÉCHÉES ET 'this'
135// ===========================================
136console.log("\n=== 5. Fonctions fléchées et 'this' ===");
137
138const enclosure = {
139  id: "A-7",
140  dinosaur: "Velociraptor",
141
142  // Méthode traditionnelle
143  checkTraditional: function() {
144    console.log(`\nEnclos ${this.id} (traditionnelle):`);
145
146    setTimeout(function() {
147      // console.log(`   Dinosaure: ${this.dinosaur}`); // undefined ! this a changé
148      console.log("   this.dinosaur = undefined (perte de contexte)");
149    }, 100);
150  },
151
152  // Solution avec une fonction fléchée
153  checkArrow: function() {
154    console.log(`\nEnclos ${this.id} (fléchée):`);
155
156    setTimeout(() => {
157      console.log(`   Dinosaure: ${this.dinosaur}`); // Fonctionne ! La fonction fléchée conserve this
158    }, 100);
159  }
160};
161
162enclosure.checkTraditional();
163
164setTimeout(() => {
165  enclosure.checkArrow();
166}, 150);
167
168// ===========================================
169// 6. 'this' DANS LES CONSTRUCTEURS ET LES CLASSES
170// ===========================================
171console.log("\n=== 6. 'this' dans les constructeurs et les classes ===");
172
173// Fonction constructeur
174function DinosaurPark(name) {
175  this.name = name;
176  this.dinosaurs = [];
177
178  this.addDinosaur = function(dino) {
179    this.dinosaurs.push(dino);
180    console.log(`${dino} ajouté à ${this.name}`);
181  };
182
183  this.getCount = function() {
184    return this.dinosaurs.length;
185  };
186}
187
188const park1 = new DinosaurPark("Parc Jurassique");
189park1.addDinosaur("T-Rex");
190park1.addDinosaur("Velociraptor");
191console.log(`Total des dinosaures: ${park1.getCount()}`);
192
193// Classe ES6
194class ModernPark {
195  constructor(name) {
196    this.name = name;
197    this.dinosaurs = [];
198  }
199
200  addDinosaur(dino) {
201    this.dinosaurs.push(dino);
202    console.log(`\n${dino} ajouté à ${this.name}`);
203  }
204
205  getCount() {
206    return this.dinosaurs.length;
207  }
208
209  displayAll() {
210    console.log(`\n${this.name} - Liste des dinosaures:`);
211    this.dinosaurs.forEach((dino, i) => {
212      console.log(`   ${i + 1}. ${dino}`);
213    });
214  }
215}
216
217const park3 = new ModernPark("Isla Nublar");
218park3.addDinosaur("Triceratops");
219park3.addDinosaur("Stegosaurus");
220park3.addDinosaur("Brachiosaurus");
221park3.displayAll();
222console.log(`Total: ${park3.getCount()}`);
223
224// ===========================================
225// 7. PROBLÈME: MÉTHODE EN TANT QUE CALLBACK
226// ===========================================
227console.log("\n=== 7. Problème: méthode en tant que callback ===");
228
229const securitySystem = {
230  level: "ÉLEVÉ",
231
232  checkSecurity: function() {
233    console.log(`Niveau de sécurité: ${this.level}`);
234  }
235};
236
237// PROBLÈME: this sera perdu
238setTimeout(securitySystem.checkSecurity, 100); // this.level = undefined
239
240// SOLUTION 1: Fonction fléchée enveloppante (wrapper)
241setTimeout(() => {
242  securitySystem.checkSecurity();
243}, 150);
244
245// SOLUTION 2: bind()
246setTimeout(securitySystem.checkSecurity.bind(securitySystem), 200);
247
248// ===========================================
249// 8. EXEMPLE COMPLET
250// ===========================================
251console.log("\n=== 8. Système de gestion du parc - exemple complet ===");
252
253class JurassicParkManagement {
254  constructor(parkName) {
255    this.parkName = parkName;
256    this.dinosaurs = [];
257    this.visitors = 0;
258    this.status = "FERMÉ";
259  }
260
261  openPark() {
262    this.status = "OUVERT";
263    console.log(`\n${this.parkName} - Parc ouvert`);
264  }
265
266  closePark() {
267    this.status = "FERMÉ";
268    console.log(`\n${this.parkName} - Parc fermé`);
269  }
270
271  addDinosaur(name, species, sector) {
272    const dino = { name, species, sector, health: 100 };
273    this.dinosaurs.push(dino);
274    console.log(`${name} (${species}) ajouté au secteur ${sector}`);
275  }
276
277  addVisitors(count) {
278    this.visitors += count;
279    console.log(`Ajout de ${count} visiteurs. Total: ${this.visitors}`);
280  }
281
282  checkDinosaur(name) {
283    const dino = this.dinosaurs.find(d => d.name === name);
284    if (dino) {
285      console.log(`\nVérification de ${name}:`);
286      console.log(`   Espèce: ${dino.species}`);
287      console.log(`   Secteur: ${dino.sector}`);
288      console.log(`   Santé: ${dino.health}%`);
289    } else {
290      console.log(`Dinosaure introuvable: ${name}`);
291    }
292  }
293
294  getDailyReport() {
295    console.log(`\nRAPPORT QUOTIDIEN - ${this.parkName}`);
296    console.log("=".repeat(50));
297    console.log(`Statut: ${this.status}`);
298    console.log(`Dinosaures: ${this.dinosaurs.length}`);
299    console.log(`Visiteurs: ${this.visitors}`);
300
301    if (this.dinosaurs.length > 0) {
302      console.log("\nListe des dinosaures:");
303      this.dinosaurs.forEach((dino, i) => {
304        console.log(`  ${i + 1}. ${dino.name} (${dino.species}) - Secteur ${dino.sector}`);
305      });
306    }
307  }
308
309  // Méthode utilisant setTimeout avec une fonction fléchée
310  scheduleFeeding(dinoName, delay = 1000) {
311    console.log(`\n⏰ Repas programmé pour ${dinoName} dans ${delay}ms`);
312
313    setTimeout(() => {
314      const dino = this.dinosaurs.find(d => d.name === dinoName);
315      if (dino) {
316        console.log(`Nourrissage de ${dinoName} dans ${this.parkName}`);
317        dino.health = Math.min(100, dino.health + 10);
318      }
319    }, delay);
320  }
321}
322
323// Démonstration du système
324const management = new JurassicParkManagement("Isla Nublar");
325
326management.openPark();
327management.addDinosaur("Rexy", "Tyrannosaurus Rex", "A");
328management.addDinosaur("Blue", "Velociraptor", "B");
329management.addDinosaur("Trike", "Triceratops", "C");
330
331management.addVisitors(500);
332management.addVisitors(300);
333
334management.checkDinosaur("Blue");
335management.getDailyReport();
336
337management.scheduleFeeding("Rexy", 500);
338management.scheduleFeeding("Blue", 600);
339
340setTimeout(() => {
341  console.log("\n--- Après le repas ---");
342  management.checkDinosaur("Rexy");
343  management.checkDinosaur("Blue");
344
345  management.closePark();
346
347  console.log("\nFélicitations ! Tu maîtrises maintenant le mot-clé 'this' !");
348}, 800);

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Que signifie le mot-clé « this » en JavaScript ?

  2. 2. Vers quoi pointe « this » à l'intérieur d'une méthode d'objet en JavaScript ?

  3. 3. Comment se comporte « this » dans les fonctions fléchées ?

  4. 4. Dans quel contexte « this » pointe-t-il vers l'objet nouvellement créé ?

Articles utiles