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

Propriétés et méthodes des objets

14 min de lecture
Dans cette leçon9

Dans le système de gestion de Jurassic Park, les propriétés et les méthodes des objets sont essentielles pour modéliser et contrôler le comportement des différents éléments de notre complexe. Dans cette leçon, nous allons examiner de plus près les propriétés et les méthodes des objets JavaScript, en découvrant des techniques avancées qui nous aident à maintenir notre parc et ses habitants en parfait état.

Les descripteurs de propriété

En JavaScript, chaque propriété d'un objet possède non seulement une valeur, mais aussi des attributs qui contrôlent la façon dont cette propriété peut être utilisée. Ces attributs sont ce qu'on appelle les descripteurs de propriété.

1const dinosaur = {
2  name: "Blue"
3};
4
5// Récupération du descripteur de la propriété
6const descriptor = Object.getOwnPropertyDescriptor(dinosaur, "name");
7console.log(descriptor);
8// Résultat : { value: "Blue", writable: true, enumerable: true, configurable: true }

Le descripteur de propriété contient les champs suivants :

  • value : la valeur actuelle de la propriété
  • writable : indique si la propriété peut être modifiée
  • enumerable : indique si la propriété apparaît lors d'une itération (par exemple dans une boucle for...in)
  • configurable : indique si le descripteur peut être modifié et si la propriété peut être supprimée

Définir des propriétés avec des descripteurs

Nous pouvons définir de nouvelles propriétés avec des descripteurs précis à l'aide de Object.defineProperty() :

1const velociraptor = {};
2
3// On définit la propriété "species" avec des descripteurs précis
4Object.defineProperty(velociraptor, "species", {
5  value: "Velociraptor",
6  writable: false, // On ne peut pas modifier la valeur
7  enumerable: true, // Apparaîtra lors d'une itération
8  configurable: false // On ne peut ni modifier le descripteur ni supprimer la propriété
9});
10
11console.log(velociraptor.species); // "Velociraptor"
12
13// La tentative de modification ne fonctionnera pas (en mode strict, elle lèvera une erreur)
14velociraptor.species = "Tyrannosaurus";
15console.log(velociraptor.species); // Toujours "Velociraptor"
16
17// La tentative de suppression ne fonctionnera pas
18delete velociraptor.species; // Renverra false
19console.log(velociraptor.species); // Toujours "Velociraptor"

Nous pouvons aussi définir plusieurs propriétés à la fois :

1const triceratops = {};
2
3Object.defineProperties(triceratops, {
4  species: {
5    value: "Triceratops",
6    writable: false
7  },
8  diet: {
9    value: "herbivore",
10    writable: false
11  },
12  age: {
13    value: 5,
14    writable: true // L'âge peut changer
15  }
16});
17
18console.log(triceratops.species); // "Triceratops"
19console.log(triceratops.diet); // "herbivore"
20console.log(triceratops.age); // 5
21
22triceratops.age = 6;
23console.log(triceratops.age); // 6

Les getters et les setters

Les getters et les setters sont des méthodes spéciales qui permettent de contrôler l'accès aux propriétés d'un objet.

1const dinosaur = {
2  _health: 100, // Par convention, le tiret bas indique qu'il s'agit d'une propriété privée
3
4  // Getter - s'exécute quand on essaie de lire la propriété health
5  get health() {
6    console.log("Lecture du niveau de santé");
7    return this._health;
8  },
9
10  // Setter - s'exécute quand on essaie d'affecter une valeur à la propriété health
11  set health(value) {
12    if (value < 0) {
13      console.log("Avertissement : tentative de régler la santé en dessous de 0");
14      this._health = 0;
15    } else if (value > 100) {
16      console.log("Avertissement : tentative de régler la santé au-dessus de 100");
17      this._health = 100;
18    } else {
19      console.log(`Réglage du niveau de santé sur ${value}`);
20      this._health = value;
21    }
22  }
23};
24
25console.log(dinosaur.health); // Déclenche le getter, résultat : 100
26dinosaur.health = 80; // Déclenche le setter
27console.log(dinosaur.health); // 80
28dinosaur.health = 150; // "Avertissement : tentative de régler la santé au-dessus de 100"
29console.log(dinosaur.health); // 100
30dinosaur.health = -20; // "Avertissement : tentative de régler la santé en dessous de 0"
31console.log(dinosaur.health); // 0

Nous pouvons aussi définir des getters et des setters à l'aide de Object.defineProperty() :

1const dinosaur = {
2  _name: "Rex",
3  _species: "Tyrannosaurus"
4};
5
6Object.defineProperty(dinosaur, "fullName", {
7  get: function() {
8    return `${this._name} the ${this._species}`;
9  },
10
11  set: function(value) {
12    // On suppose que value a le format "Nom the Espèce"
13    const parts = value.split(" the ");
14    if (parts.length === 2) {
15      this._name = parts[0];
16      this._species = parts[1];
17    } else {
18      console.log("Format invalide. Utilise : 'Nom the Espèce'");
19    }
20  },
21
22  enumerable: true,
23  configurable: true
24});
25
26console.log(dinosaur.fullName); // "Rex the Tyrannosaurus"
27dinosaur.fullName = "Blue the Velociraptor";
28console.log(dinosaur._name); // "Blue"
29console.log(dinosaur._species); // "Velociraptor"

Les méthodes d'objet et leurs propriétés avancées

Lier this - la méthode bind

Dans les méthodes, le mot-clé this fait référence à l'objet sur lequel la méthode est appelée. Cependant, dans certains cas (par exemple quand on passe une méthode comme callback), on peut perdre le contexte this. La méthode bind permet de fixer explicitement la valeur de this.

1const rex = {
2  name: "Rex",
3  makeSound: function() {
4    console.log(`${this.name} rugit très fort !`);
5  }
6};
7
8// Fonctionnement normal
9rex.makeSound(); // "Rex rugit très fort !"
10
11// Utilisation problématique - perte du contexte this
12const rexSound = rex.makeSound;
13// rexSound(); // Erreur : this.name vaut undefined
14
15// Solution - utiliser bind pour fixer la valeur de this
16const boundRexSound = rex.makeSound.bind(rex);
17boundRexSound(); // "Rex rugit très fort !"
18
19// On peut aussi lier this à un autre objet
20const blue = { name: "Blue" };
21const blueSound = rex.makeSound.bind(blue);
22blueSound(); // "Blue rugit très fort !"

Les méthodes call et apply

Les méthodes call et apply permettent d'appeler une fonction avec une valeur de this et des arguments précis :

1function attack(target, damage) {
2  console.log(`${this.name} attaque ${target} et inflige ${damage} points de dégâts !`);
3}
4
5const trex = { name: "Tyrannosaurus Rex" };
6const raptor = { name: "Velociraptor" };
7
8// On utilise call avec des arguments passés explicitement
9attack.call(trex, "Triceratops", 50);
10// "Tyrannosaurus Rex attaque Triceratops et inflige 50 points de dégâts !"
11
12// On utilise apply avec les arguments dans un tableau
13attack.apply(raptor, ["Gallimimus", 30]);
14// "Velociraptor attaque Gallimimus et inflige 30 points de dégâts !"

Les propriétés d'objet sous forme de symboles

Les symboles fournissent des identifiants uniques qui peuvent servir de clés de propriétés d'objet. Ils sont particulièrement utiles pour créer des propriétés "privées" ou pour étendre des objets sans risque de collision de noms.

1// Création de symboles
2const healthSymbol = Symbol('health');
3const nameSymbol = Symbol('name');
4
5const dinosaur = {
6  [nameSymbol]: "Blue",
7  [healthSymbol]: 100,
8  species: "Velociraptor"
9};
10
11console.log(dinosaur[nameSymbol]); // "Blue"
12console.log(dinosaur[healthSymbol]); // 100
13
14// Les symboles ne sont pas visibles dans les boucles for...in
15for (let key in dinosaur) {
16  console.log(key); // Seulement "species", sans nameSymbol ni healthSymbol
17}
18
19// Ni dans Object.keys()
20console.log(Object.keys(dinosaur)); // ["species"]
21
22// Mais on peut les obtenir avec Object.getOwnPropertySymbols()
23console.log(Object.getOwnPropertySymbols(dinosaur)); // [Symbol(name), Symbol(health)]

Les symboles bien connus

JavaScript définit plusieurs "symboles bien connus", qui permettent aux classes d'adapter leur comportement. Le plus utilisé est Symbol.iterator, qui permet aux objets d'être itérables.

1// On crée un objet DinosaurPack qui sera itérable
2class DinosaurPack {
3  constructor() {
4    this.dinosaurs = [];
5  }
6
7  addDinosaur(name, species) {
8    this.dinosaurs.push({ name, species });
9  }
10
11  // Implémentation de l'itérabilité
12  [Symbol.iterator]() {
13    let index = 0;
14    const dinosaurs = this.dinosaurs;
15
16    return {
17      next: function() {
18        if (index < dinosaurs.length) {
19          return { value: dinosaurs[index++], done: false };
20        } else {
21          return { done: true };
22        }
23      }
24    };
25  }
26}
27
28const pack = new DinosaurPack();
29pack.addDinosaur("Blue", "Velociraptor");
30pack.addDinosaur("Delta", "Velociraptor");
31pack.addDinosaur("Echo", "Velociraptor");
32pack.addDinosaur("Charlie", "Velociraptor");
33
34// Maintenant, on peut itérer sur notre objet
35for (const dino of pack) {
36  console.log(`${dino.name} est un ${dino.species}`);
37}
38// Résultat :
39// "Blue est un Velociraptor"
40// "Delta est un Velociraptor"
41// "Echo est un Velociraptor"
42// "Charlie est un Velociraptor"

Geler, sceller et empêcher l'extension des objets

JavaScript propose trois méthodes pour contrôler dans quelle mesure on peut modifier les objets :

Object.freeze()

Gèle l'objet (de façon superficielle : les objets imbriqués peuvent toujours être modifiés) - on ne peut ni modifier les propriétés existantes ni en ajouter de nouvelles.

1const securityProtocol = {
2  level: "Maximum",
3  status: "Active",
4  lastUpdated: "2023-01-15"
5};
6
7// On gèle l'objet
8Object.freeze(securityProtocol);
9
10// Les tentatives de modification ne fonctionneront pas
11securityProtocol.level = "Minimum"; // Ne changera pas
12securityProtocol.newProperty = "Value"; // Ne sera pas ajoutée
13
14console.log(securityProtocol.level); // Toujours "Maximum"
15console.log(securityProtocol.newProperty); // undefined
16
17console.log(Object.isFrozen(securityProtocol)); // true

Object.seal()

Scelle l'objet - on peut modifier les propriétés existantes, mais on ne peut ni en ajouter de nouvelles ni supprimer celles qui existent.

1const dinoStats = {
2  health: 100,
3  hunger: 50
4};
5
6// On scelle l'objet
7Object.seal(dinoStats);
8
9// On peut modifier les propriétés existantes
10dinoStats.health = 80;
11console.log(dinoStats.health); // 80
12
13// Mais on ne peut pas en ajouter de nouvelles
14dinoStats.happiness = 90; // Ne fonctionnera pas
15console.log(dinoStats.happiness); // undefined
16
17// Ni supprimer celles qui existent
18delete dinoStats.hunger; // Ne fonctionnera pas
19console.log(dinoStats.hunger); // Toujours 50
20
21console.log(Object.isSealed(dinoStats)); // true

Object.preventExtensions()

Empêche l'extension de l'objet - on ne peut pas ajouter de nouvelles propriétés, mais on peut modifier et supprimer celles qui existent.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor"
4};
5
6// On empêche l'extension
7Object.preventExtensions(dinosaur);
8
9// On ne peut pas ajouter de nouvelles propriétés
10dinosaur.age = 4; // Ne fonctionnera pas
11console.log(dinosaur.age); // undefined
12
13// Mais on peut modifier celles qui existent
14dinosaur.name = "Delta";
15console.log(dinosaur.name); // "Delta"
16
17// Et supprimer celles qui existent
18delete dinosaur.species;
19console.log(dinosaur.species); // undefined
20
21console.log(Object.isExtensible(dinosaur)); // false

L'objet Proxy

L'objet Proxy permet d'intercepter et de personnaliser les opérations de base sur les objets.

1const dinosaur = {
2  name: "Blue",
3  species: "Velociraptor",
4  health: 100,
5  hunger: 50,
6  aggressionLevel: 8
7};
8
9// On crée un proxy pour l'objet dinosaur
10const monitoredDinosaur = new Proxy(dinosaur, {
11  // On intercepte l'opération de lecture d'une propriété
12  get: function(target, prop, receiver) {
13    console.log(`Lecture de la propriété : ${prop}`);
14    return Reflect.get(target, prop, receiver);
15  },
16
17  // On intercepte l'opération d'écriture d'une propriété
18  set: function(target, prop, value, receiver) {
19    console.log(`Réglage de ${prop} sur ${value}`);
20
21    // On ajoute une validation pour certaines propriétés
22    if (prop === 'health') {
23      if (value < 0) value = 0;
24      if (value > 100) value = 100;
25
26      // Journalisation de la variation de santé
27      const oldValue = target[prop];
28      const change = value - oldValue;
29      console.log(`Variation de la santé : ${change > 0 ? '+' : ''}${change}%`);
30
31      // On vérifie que le dinosaure n'est pas gravement blessé
32      if (value < 20 && oldValue >= 20) {
33        console.log("ALARME : le dinosaure est gravement blessé !");
34      }
35    }
36
37    if (prop === 'aggressionLevel') {
38      if (value > 7) {
39        console.log("ATTENTION : niveau d'agressivité élevé - il faut renforcer les mesures de sécurité !");
40      }
41    }
42
43    return Reflect.set(target, prop, value, receiver);
44  },
45
46  // On intercepte l'opération de suppression d'une propriété
47  deleteProperty: function(target, prop) {
48    console.log(`Tentative de suppression de ${prop} - opération interdite pour la sécurité des données`);
49    return false; // On n'autorise pas la suppression des propriétés
50  }
51});
52
53// On teste notre proxy
54console.log(monitoredDinosaur.name); // "Lecture de la propriété : name", "Blue"
55monitoredDinosaur.health = 90; // "Réglage de health sur 90", "Variation de la santé : -10%"
56monitoredDinosaur.health = 15; // "Réglage de health sur 15", "Variation de la santé : -75%", "ALARME : le dinosaure est gravement blessé !"
57monitoredDinosaur.aggressionLevel = 9; // "Réglage de aggressionLevel sur 9", "ATTENTION : niveau d'agressivité élevé..."
58delete monitoredDinosaur.hunger; // "Tentative de suppression de hunger - opération interdite pour la sécurité des données"

Application pratique : un système de surveillance de la santé des dinosaures

Combinons plusieurs des techniques découvertes pour créer un système avancé de surveillance de la santé des dinosaures de notre parc :

1class DinosaurHealthMonitor {
2  constructor() {
3    this.dinosaurs = new Map();
4    this.alarmThreshold = 30;
5    this.criticalThreshold = 15;
6    this.lastCheckTime = null;
7  }
8
9  // On ajoute un dinosaure au système de surveillance
10  addDinosaur(id, name, species) {
11    if (this.dinosaurs.has(id)) {
12      console.log(`Le dinosaure avec l'ID ${id} existe déjà dans le système.`);
13      return false;
14    }
15
16    const dinosaurData = {
17      id,
18      name,
19      species,
20      _health: 100,
21      _lastFed: new Date(),
22      _status: "Healthy",
23
24      // On utilise un getter et un setter pour la santé
25      get health() {
26        return this._health;
27      },
28
29      set health(value) {
30        const oldHealth = this._health;
31        this._health = Math.max(0, Math.min(100, value)); // Limitation à la plage 0-100
32
33        // Mise à jour du statut en fonction de la santé
34        if (this._health === 0) {
35          this._status = "Critical - Emergency Response Required";
36        } else if (this._health < 15) {
37          this._status = "Critical";
38        } else if (this._health < 30) {
39          this._status = "Poor - Medical Attention Required";
40        } else if (this._health < 60) {
41          this._status = "Fair";
42        } else if (this._health < 90) {
43          this._status = "Good";
44        } else {
45          this._status = "Excellent";
46        }
47
48        return this._health;
49      },
50
51      // Méthode pour nourrir le dinosaure
52      feed() {
53        const now = new Date();
54        const hoursSinceLastFed = (now - this._lastFed) / (1000 * 60 * 60);
55
56        // L'effet du repas dépend du temps écoulé depuis le dernier repas du dinosaure
57        if (hoursSinceLastFed < 1) {
58          console.log(`${this.name} n'a pas faim (nourri il y a moins d'une heure).`);
59          return false;
60        } else {
61          const healthGain = Math.min(10, hoursSinceLastFed * 2); // 10 points de santé au maximum
62          this.health += healthGain;
63          this._lastFed = now;
64
65          console.log(`${this.name} a été nourri. +${healthGain.toFixed(1)} de santé. Niveau actuel : ${this.health}%`);
66          return true;
67        }
68      }
69    };
70
71    // On utilise Object.seal pour empêcher l'ajout de nouvelles propriétés
72    Object.seal(dinosaurData);
73
74    // On ajoute le dinosaure à la Map
75    this.dinosaurs.set(id, dinosaurData);
76    console.log(`Dinosaure ${name} (${species}) ajouté au système de surveillance de la santé.`);
77    return true;
78  }
79
80  // Récupération des informations sur un dinosaure
81  getDinosaurInfo(id) {
82    if (!this.dinosaurs.has(id)) {
83      return `Le dinosaure avec l'ID ${id} n'existe pas dans le système.`;
84    }
85
86    const dino = this.dinosaurs.get(id);
87    return {
88      id: dino.id,
89      name: dino.name,
90      species: dino.species,
91      health: dino.health,
92      status: dino._status,
93      lastFed: dino._lastFed
94    };
95  }
96
97  // Méthode qui simule le passage du temps, ce qui fait baisser la santé des dinosaures
98  simulateTimePass(hours) {
99    console.log(`Simulation du passage de ${hours} heures...`);
100
101    this.dinosaurs.forEach(dino => {
102      // Baisse de la santé - le rythme dépend de l'espèce
103      let healthReduction = hours;
104
105      if (dino.species.toLowerCase().includes('tyrannosaurus')) {
106        healthReduction *= 1.5; // Le Tyrannosaurus perd sa santé plus vite
107      } else if (dino.species.toLowerCase().includes('velociraptor')) {
108        healthReduction *= 1.2; // Les Velociraptors aussi, assez vite
109      }
110
111      const newHealth = dino.health - healthReduction;
112      dino.health = newHealth;
113
114      console.log(`${dino.name} : santé réduite de ${healthReduction.toFixed(1)} à ${dino.health}% (${dino._status})`);
115    });
116
117    this.lastCheckTime = new Date();
118    this.checkAlarms();
119  }
120
121  // Vérification des alarmes de santé
122  checkAlarms() {
123    console.log("Vérification des alarmes de santé...");
124
125    const criticalDinos = [];
126    const alarmDinos = [];
127
128    this.dinosaurs.forEach(dino => {
129      if (dino.health <= this.criticalThreshold) {
130        criticalDinos.push(dino);
131      } else if (dino.health <= this.alarmThreshold) {
132        alarmDinos.push(dino);
133      }
134    });
135
136    if (criticalDinos.length > 0) {
137      console.log("=== ÉTAT DE SANTÉ CRITIQUE ===");
138      criticalDinos.forEach(dino => {
139        console.log(`- ${dino.name} (${dino.species}) : ${dino.health}% - INTERVENTION IMMÉDIATE REQUISE !`);
140      });
141    }
142
143    if (alarmDinos.length > 0) {
144      console.log("=== ALARME SANTÉ ===");
145      alarmDinos.forEach(dino => {
146        console.log(`- ${dino.name} (${dino.species}) : ${dino.health}% - Nécessite des soins vétérinaires`);
147      });
148    }
149
150    if (criticalDinos.length === 0 && alarmDinos.length === 0) {
151      console.log("Tous les dinosaures sont en bonne santé.");
152    }
153  }
154
155  // Génération d'un rapport pour tous les dinosaures
156  generateHealthReport() {
157    const now = new Date();
158    let report = `=== RAPPORT DE SANTÉ DES DINOSAURES (généré : ${now.toLocaleString()}) ===\n`;
159
160    if (this.dinosaurs.size === 0) {
161      report += "Aucun dinosaure dans le système de surveillance.\n";
162    } else {
163      // Tri des dinosaures par niveau de santé (en commençant par le plus bas)
164      const sortedDinos = [...this.dinosaurs.values()].sort((a, b) => a.health - b.health);
165
166      sortedDinos.forEach(dino => {
167        report += `${dino.name} (${dino.species})\n`;
168        report += `  Santé : ${dino.health}% (${dino._status})\n`;
169        report += `  Dernier repas : ${dino._lastFed.toLocaleString()}\n`;
170
171        // On ajoute des recommandations
172        if (dino.health < this.criticalThreshold) {
173          report += "  RECOMMANDATION : intervention vétérinaire immédiate ! Transfert en unité de soins intensifs.\n";
174        } else if (dino.health < this.alarmThreshold) {
175          report += "  RECOMMANDATION : fréquence accrue des repas et des contrôles vétérinaires.\n";
176        }
177
178        report += "\n";
179      });
180    }
181
182    return report;
183  }
184}
185
186// Exemple d'utilisation
187const healthMonitor = new DinosaurHealthMonitor();
188
189// On ajoute quelques dinosaures
190healthMonitor.addDinosaur("TRX-01", "Rex", "Tyrannosaurus Rex");
191healthMonitor.addDinosaur("VEL-01", "Blue", "Velociraptor");
192healthMonitor.addDinosaur("TRI-01", "Trixie", "Triceratops");
193healthMonitor.addDinosaur("BRACH-01", "Bronty", "Brachiosaurus");
194
195// On génère le rapport initial
196console.log(healthMonitor.generateHealthReport());
197
198// On simule le passage du temps
199healthMonitor.simulateTimePass(24); // 24 heures
200
201// On nourrit certains dinosaures
202const rex = healthMonitor.dinosaurs.get("TRX-01");
203const blue = healthMonitor.dinosaurs.get("VEL-01");
204rex.feed();
205blue.feed();
206
207// On simule un nouveau passage du temps
208healthMonitor.simulateTimePass(48); // 48 heures
209
210// On génère le rapport final
211console.log(healthMonitor.generateHealthReport());

Résumé

Dans cette leçon, nous avons découvert les propriétés et les méthodes avancées des objets JavaScript :

  1. Les descripteurs de propriété - permettent un contrôle précis des attributs des propriétés
  2. Les getters et les setters - permettent d'exécuter du code lors de la lecture ou de l'écriture d'une propriété
  3. La liaison de this - les méthodes bind, call et apply aident à contrôler le contexte d'une fonction
  4. Les symboles - fournissent des identifiants uniques pour les propriétés d'objet
  5. Le gel des objets - les méthodes freeze, seal et preventExtensions permettent de limiter les modifications des objets
  6. Proxy - permet d'intercepter et de personnaliser les opérations de base sur les objets

Ces techniques sont indispensables pour créer des systèmes complexes, comme notre système de surveillance de la santé des dinosaures à Jurassic Park. Elles permettent de concevoir des solutions logicielles plus fiables, plus sûres et plus élégantes.

Code de cette leçon : index.js
1// Propriétés et méthodes des objets - Parc Jurassique
2console.log("Propriétés et Méthodes des Objets");
3console.log("Opérations avancées sur les objets\n");
4
5// ===========================================
6// 1. ACCÈS AUX PROPRIÉTÉS
7// ===========================================
8console.log("=== 1. Accès aux propriétés ===");
9
10const dinosaur = {
11  name: "T-Rex",
12  species: "Tyrannosaurus Rex",
13  age: 8,
14  "sector-id": "A-7", // Propriété avec un tiret
15  weight: 8000
16};
17
18// Notation par point
19console.log("\nNotation par point:");
20console.log(`   name: ${dinosaur.name}`);
21console.log(`   age: ${dinosaur.age}`);
22
23// Notation par crochets
24console.log("\nNotation par crochets:");
25console.log(`   ['species']: ${dinosaur['species']}`);
26console.log(`   ['sector-id']: ${dinosaur['sector-id']}`); // Obligatoire pour les tirets
27
28// Accès dynamique
29const prop = "weight";
30console.log(`\nAccès dynamique [${prop}]: ${dinosaur[prop]}`);
31
32// ===========================================
33// 2. AJOUT ET MODIFICATION DE PROPRIÉTÉS
34// ===========================================
35console.log("\n=== 2. Ajout et modification ===");
36
37const dino = {
38  name: "Blue",
39  species: "Velociraptor"
40};
41
42console.log("\nInitialement:", dino);
43
44// Ajout de nouvelles propriétés
45dino.age = 5;
46dino.weight = 80;
47dino["sector"] = "B";
48
49console.log("Après ajout:", dino);
50
51// Modification de l'existant
52dino.age = 6;
53dino.weight = 85;
54
55console.log("Après modification:", dino);
56
57// ===========================================
58// 3. SUPPRESSION DE PROPRIÉTÉS
59// ===========================================
60console.log("\n=== 3. Suppression de propriétés ===");
61
62const tempDino = {
63  name: "Charlie",
64  species: "Velociraptor",
65  tempId: 12345,
66  debugMode: true
67};
68
69console.log("\nAvant suppression:", tempDino);
70
71delete tempDino.tempId;
72delete tempDino.debugMode;
73
74console.log("Après suppression:", tempDino);
75
76// ===========================================
77// 4. VÉRIFICATION DE L'EXISTENCE DE PROPRIÉTÉS
78// ===========================================
79console.log("\n=== 4. Vérification de l'existence ===");
80
81const checkDino = {
82  name: "Rexy",
83  age: 8,
84  sector: null
85};
86
87// Opérateur 'in'
88console.log("\nOpérateur 'in':");
89console.log(`   'name' in checkDino: ${'name' in checkDino}`);
90console.log(`   'weight' in checkDino: ${'weight' in checkDino}`);
91console.log(`   'sector' in checkDino: ${'sector' in checkDino}`); // true malgré null !
92
93// hasOwnProperty()
94console.log("\nhasOwnProperty():");
95console.log(`   hasOwnProperty('name'): ${checkDino.hasOwnProperty('name')}`);
96console.log(`   hasOwnProperty('toString'): ${checkDino.hasOwnProperty('toString')}`); // false (héritée)
97
98// Vérification avec undefined
99console.log("\nVérification undefined:");
100console.log(`   name !== undefined: ${checkDino.name !== undefined}`);
101console.log(`   weight !== undefined: ${checkDino.weight !== undefined}`);
102
103// ===========================================
104// 5. ITÉRATION SUR LES PROPRIÉTÉS
105// ===========================================
106console.log("\n=== 5. Itération sur les propriétés ===");
107
108const iterDino = {
109  name: "Trike",
110  species: "Triceratops",
111  age: 7,
112  weight: 9000,
113  sector: "C"
114};
115
116// for...in
117console.log("\nfor...in:");
118for (const key in iterDino) {
119  console.log(`   ${key}: ${iterDino[key]}`);
120}
121
122// Object.keys()
123console.log("\nObject.keys():");
124const keys = Object.keys(iterDino);
125console.log(`   Clés: ${keys.join(", ")}`);
126
127// Object.values()
128console.log("\nObject.values():");
129const values = Object.values(iterDino);
130console.log(`   Valeurs: ${values.join(", ")}`);
131
132// Object.entries()
133console.log("\nObject.entries():");
134Object.entries(iterDino).forEach(([key, value]) => {
135  console.log(`   ${key} => ${value}`);
136});
137
138// ===========================================
139// 6. MÉTHODES DES OBJETS
140// ===========================================
141console.log("\n=== 6. Méthodes des objets ===");
142
143const methodDino = {
144  name: "Stego",
145  species: "Stegosaurus",
146  health: 85,
147
148  // Méthode traditionnelle
149  getInfo: function() {
150    return `${this.name} (${this.species})`;
151  },
152
153  // Méthode abrégée (ES6)
154  roar() {
155    return `${this.name} rugit: ROAARRR!`;
156  },
157
158  // Méthode avec des paramètres
159  updateHealth(change) {
160    this.health += change;
161    if (this.health > 100) this.health = 100;
162    if (this.health < 0) this.health = 0;
163    return `Santé: ${this.health}%`;
164  },
165
166  // Méthode qui retourne un objet
167  getStats() {
168    return {
169      name: this.name,
170      health: this.health,
171      status: this.health >= 80 ? "En bonne santé" : "Nécessite de l'attention"
172    };
173  }
174};
175
176console.log("\n", methodDino.getInfo());
177console.log("   ", methodDino.roar());
178console.log("   ", methodDino.updateHealth(10));
179console.log("   Stats:", methodDino.getStats());
180
181// ===========================================
182// 7. THIS DANS LES MÉTHODES
183// ===========================================
184console.log("\n=== 7. Contexte de 'this' dans les méthodes ===");
185
186const thisDino = {
187  name: "Brachio",
188  sector: "D",
189
190  showInfo() {
191    console.log(`\n${this.name} dans le secteur ${this.sector}`);
192  },
193
194  // Problème avec une fonction imbriquée
195  showWithTraditional() {
196    console.log(`\n${this.name}:`);
197
198    ["Nourrissage", "Examen", "Nettoyage"].forEach(function(task) {
199      // this ici NE fait PAS référence à thisDino !
200      console.log(`   - ${task}`); // On ne peut pas utiliser this.name
201    });
202  },
203
204  // Solution avec une fonction fléchée
205  showWithArrow() {
206    console.log(`\n${this.name}:`);
207
208    ["Nourrissage", "Examen", "Nettoyage"].forEach(task => {
209      console.log(`   - ${task} pour ${this.name}`); // this fonctionne !
210    });
211  }
212};
213
214thisDino.showInfo();
215thisDino.showWithTraditional();
216thisDino.showWithArrow();
217
218// ===========================================
219// 8. NOMS DE PROPRIÉTÉS CALCULÉS
220// ===========================================
221console.log("\n=== 8. Noms de propriétés calculés ===");
222
223const propName = "species";
224const propValue = "Velociraptor";
225const sectorNumber = 7;
226
227const computedDino = {
228  name: "Echo",
229  [propName]: propValue, // Nom de propriété calculé
230  [`sector-${sectorNumber}`]: true,
231  [`health_${Date.now()}`]: 95
232};
233
234console.log("\nObjet avec des noms calculés:");
235console.log(computedDino);
236
237// ===========================================
238// 9. DESCRIPTEURS DE PROPRIÉTÉS
239// ===========================================
240console.log("\n=== 9. Descripteurs de propriétés ===");
241
242const descriptorDino = {};
243
244// Définition de propriétés avec des descripteurs
245Object.defineProperty(descriptorDino, 'name', {
246  value: "Rex",
247  writable: true,
248  enumerable: true,
249  configurable: true
250});
251
252Object.defineProperty(descriptorDino, 'id', {
253  value: 12345,
254  writable: false, // Lecture seule
255  enumerable: false, // N'apparaît pas dans for...in
256  configurable: false // Impossible de supprimer ou de modifier le descripteur
257});
258
259console.log("\nPropriétés:");
260console.log(`   name: ${descriptorDino.name}`);
261console.log(`   id: ${descriptorDino.id}`);
262
263console.log("\nTentative de modification:");
264descriptorDino.name = "Rexy"; // Fonctionne
265console.log(`   name après changement: ${descriptorDino.name}`);
266
267descriptorDino.id = 99999; // Ne fonctionne pas (writable: false)
268console.log(`   id après tentative de changement: ${descriptorDino.id}`);
269
270console.log("\nfor...in (id n'apparaît pas):");
271for (const key in descriptorDino) {
272  console.log(`   ${key}: ${descriptorDino[key]}`);
273}
274
275// ===========================================
276// 10. EXEMPLE COMPLET
277// ===========================================
278console.log("\n=== 10. Système de gestion d'un dinosaure - exemple complet ===");
279
280class DinosaurManager {
281  constructor(name, species) {
282    // Propriétés de base
283    this._name = name;
284    this._species = species;
285    this._health = 100;
286    this._fed = false;
287    this._lastFed = null;
288    this._activities = [];
289
290    // Propriétés calculées
291    Object.defineProperty(this, 'id', {
292      value: `DINO-${Date.now()}`,
293      writable: false,
294      enumerable: true
295    });
296  }
297
298  // Getters et setters
299  get name() {
300    return this._name;
301  }
302
303  get species() {
304    return this._species;
305  }
306
307  get health() {
308    return this._health;
309  }
310
311  set health(value) {
312    if (value < 0) value = 0;
313    if (value > 100) value = 100;
314
315    const oldHealth = this._health;
316    this._health = value;
317
318    this.logActivity(`Santé changée de ${oldHealth}% à ${value}%`);
319  }
320
321  get status() {
322    if (this._health >= 80) return "Excellent";
323    if (this._health >= 50) return "Bon";
324    if (this._health >= 20) return "Faible";
325    return "Critique";
326  }
327
328  get needsFeeding() {
329    if (!this._lastFed) return true;
330    const hoursSinceLastFed = (Date.now() - this._lastFed) / (1000 * 60 * 60);
331    return hoursSinceLastFed > 8;
332  }
333
334  // Méthodes d'action
335  feed() {
336    this._fed = true;
337    this._lastFed = Date.now();
338    this.health += 5;
339    this.logActivity("Nourri");
340    return `${this._name} a été nourri`;
341  }
342
343  heal(amount) {
344    this.health += amount;
345    this.logActivity(`Soigné (+${amount}% de santé)`);
346    return `${this._name} soigné de ${amount}%`;
347  }
348
349  exercise() {
350    this.health -= 5;
351    this.logActivity("Exercice");
352    return `${this._name} a fait de l'exercice`;
353  }
354
355  // Méthodes auxiliaires
356  logActivity(activity) {
357    this._activities.push({
358      activity,
359      timestamp: new Date().toISOString(),
360      health: this._health
361    });
362  }
363
364  getActivityLog(limit = 5) {
365    return this._activities.slice(-limit);
366  }
367
368  getInfo() {
369    return {
370      id: this.id,
371      name: this._name,
372      species: this._species,
373      health: this._health,
374      status: this.status,
375      fed: this._fed,
376      needsFeeding: this.needsFeeding,
377      activitiesCount: this._activities.length
378    };
379  }
380
381  displayReport() {
382    console.log(`\n==================================================`);
383    console.log(`RAPPORT DU DINOSAURE: ${this._name}`);
384    console.log("=".repeat(50));
385
386    const info = this.getInfo();
387    console.log(`ID: ${info.id}`);
388    console.log(`Espèce: ${info.species}`);
389    console.log(`Santé: ${info.health}% (${info.status})`);
390    console.log(`Nourri: ${info.fed ? "Oui" : "Non"}`);
391    console.log(`Nécessite nourriture: ${info.needsFeeding ? "Oui" : "Non"}`);
392    console.log(`Nombre d'activités: ${info.activitiesCount}`);
393
394    const recentActivities = this.getActivityLog(5);
395    if (recentActivities.length > 0) {
396      console.log(`\nDernières activités:`);
397      recentActivities.forEach((act, i) => {
398        const time = new Date(act.timestamp).toLocaleTimeString();
399        console.log(`  ${i + 1}. [${time}] ${act.activity} (santé: ${act.health}%)`);
400      });
401    }
402  }
403
404  // Méthode statique
405  static compare(dino1, dino2) {
406    console.log(`\nComparaison: ${dino1.name} vs ${dino2.name}`);
407    console.log(`   Santé: ${dino1.health}% vs ${dino2.health}%`);
408    console.log(`   Statut: ${dino1.status} vs ${dino2.status}`);
409
410    if (dino1.health > dino2.health) {
411      console.log(`   ${dino1.name} est en meilleure santé`);
412    } else if (dino2.health > dino1.health) {
413      console.log(`   ${dino2.name} est en meilleure santé`);
414    } else {
415      console.log(`   Les deux ont la même santé`);
416    }
417  }
418}
419
420// Démonstration du système
421const rexy = new DinosaurManager("Rexy", "T-Rex");
422const blue = new DinosaurManager("Blue", "Velociraptor");
423
424console.log();
425console.log(rexy.feed());
426console.log(rexy.exercise());
427console.log(rexy.heal(10));
428
429console.log();
430console.log(blue.feed());
431console.log(blue.exercise());
432console.log(blue.exercise());
433
434rexy.displayReport();
435blue.displayReport();
436
437DinosaurManager.compare(rexy, blue);
438
439console.log("\nFélicitations ! Tu maîtrises maintenant les propriétés et les méthodes des objets !");

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. À quoi sert Object.defineProperty() en JavaScript ?

  2. 2. À quoi servent les getters (get) et les setters (set) dans les objets JavaScript ?

Tâches pratiques dans le jeu

  • Clique dans l'ordre

    Range les éléments de la définition d'un getter dans un objet :

Articles utiles