
Objets et prototypes en JavaScript
Plongez dans les objets JavaScript : creation (litteraux, constructeurs), proprietes, 'this', et le mecanisme fondamental de l'heritage prototypal et la syntaxe 'class'.
Les objets : Structures de données fondamentales
Les objets sont au coeur de JavaScript. Presque tout en JavaScript peut être considéré ou se comporte comme un objet, des structures de données que vous créez aux éléments intégrés comme les tableaux, les dates, et même les fonctions. Un objet est essentiellement une collection de paires clé-valeur, où les clés sont généralement des chaînes de caractères (ou des Symboles) et les valeurs peuvent être de n'importe quel type de données (primitives, autres objets, fonctions).
Dans Node.js, vous manipulerez constamment des objets : pour représenter des données (comme des utilisateurs, des produits), configurer des options, interagir avec les résultats de modules (comme les objets `request` et `response` dans Express), ou modéliser des entités de base de données. Comprendre comment créer, manipuler et structurer les objets est donc absolument essentiel.
Au-delà de la simple structure clé-valeur, le fonctionnement des objets en JavaScript est intimement lié au concept d'héritage prototypal. Contrairement aux langages orientés objet classiques basés sur les classes, JavaScript utilise un système où les objets héritent directement d'autres objets via un mécanisme de "prototype". Même la syntaxe `class` introduite en ES6 est une surcouche sur ce système prototypal.
Création et manipulation d'objets
Il existe plusieurs façons de créer des objets en JavaScript :
1. Littéraux d'objet (Object Literals) : C'est la méthode la plus simple et la plus courante. On utilise des accolades `{}` pour définir l'objet et ses propriétés.
const utilisateur = {
nom: "Alice",
email: "alice@example.com",
age: 30,
estActif: true,
// Une propriété peut être une fonction (on appelle ça une méthode)
saluer: function() {
console.log(`Bonjour, je suis ${this.nom}!`); // 'this' réfère à l'objet 'utilisateur'
},
// Syntaxe concise pour les méthodes (ES6)
afficherEmail() {
console.log(`Mon email est ${this.email}`);
}
};
// Accès aux propriétés
console.log(utilisateur.nom); // "Alice"
console.log(utilisateur['email']); // "alice@example.com" (notation crochet utile pour clés dynamiques)
// Appel des méthodes
utilisateur.saluer(); // "Bonjour, je suis Alice!"
utilisateur.afficherEmail(); // "Mon email est alice@example.com"2. Constructeur `new Object()` : Moins courant et généralement moins performant que les littéraux.
const voiture = new Object();
voiture.marque = "Toyota";
voiture.modele = "Corolla";3. Fonctions constructeur (Constructor Functions) : Permet de créer des "plans" pour fabriquer des objets ayant la même structure. Par convention, leur nom commence par une majuscule. L'opérateur `new` est utilisé pour créer une instance.
function Personne(nom, age) {
// 'this' réfère au nouvel objet en cours de création
this.nom = nom;
this.age = age;
// Méthode définie directement dans le constructeur (moins efficace pour la mémoire)
// this.sePresenter = function() {
// console.log(`Je m'appelle ${this.nom} et j'ai ${this.age} ans.`);
// };
}
// Il est préférable d'ajouter les méthodes au prototype (voir section suivante)
Personne.prototype.sePresenter = function() {
console.log(`Je m'appelle ${this.nom} et j'ai ${this.age} ans.`);
};
const personne1 = new Personne("Bob", 25);
const personne2 = new Personne("Charlie", 35);
personne1.sePresenter(); // "Je m'appelle Bob et j'ai 25 ans."
personne2.sePresenter(); // "Je m'appelle Charlie et j'ai 35 ans."Manipulation des propriétés :
- Accès : Via la notation point (`.`) ou crochet (`[]`). La notation crochet est nécessaire si la clé contient des espaces ou caractères spéciaux, ou si elle est stockée dans une variable.
let propriete = "age"; console.log(utilisateur[propriete]); // 30 - Ajout/Modification : Simplement en assignant une valeur.
utilisateur.ville = "Paris"; // Ajout de la propriété 'ville' utilisateur.age = 31; // Modification de la propriété 'age' - Suppression : Avec l'opérateur `delete`.
delete utilisateur.estActif; console.log(utilisateur.estActif); // undefined
Le mot-clé `this` : Sa valeur dépend du contexte d'appel de la fonction.
- Dans une méthode d'objet (`objet.methode()`), `this` référence l'objet lui-même.
- Dans une fonction appelée normalement (pas comme méthode ou constructeur), `this` référence l'objet global (`global` en Node.js en mode non-strict, `undefined` en mode strict).
- Dans une fonction constructeur appelée avec `new`, `this` référence le nouvel objet créé.
- Dans une fonction fléchée, `this` conserve la valeur qu'il avait dans la portée où la fonction fléchée a été définie (portée lexicale).
L'héritage prototypal : La chaîne des prototypes
C'est le mécanisme fondamental d'héritage en JavaScript. Chaque objet JavaScript possède une référence interne (souvent conceptualisée comme `[[Prototype]]` ou accessible via la méthode non standard `__proto__` ou la méthode standard `Object.getPrototypeOf()`) vers un autre objet, appelé son prototype. Cet objet prototype a lui-même son propre prototype, et ainsi de suite, formant une chaîne de prototypes. La chaîne se termine lorsque l'on atteint un prototype qui est `null`.
Comment ça marche ? Lorsque vous tentez d'accéder à une propriété (ou une méthode) sur un objet :
- JavaScript cherche d'abord la propriété directement sur l'objet lui-même.
- S'il ne la trouve pas, il remonte la chaîne et cherche sur l'objet prototype.
- S'il ne la trouve toujours pas, il continue à remonter la chaîne de prototype en prototype.
- S'il atteint la fin de la chaîne (`null`) sans trouver la propriété, il renvoie `undefined`.
C'est ainsi que fonctionne l'héritage : les objets "héritent" des propriétés et méthodes de leurs prototypes. Par exemple, tous les tableaux (Arrays) créés en JavaScript héritent des méthodes comme `push()`, `pop()`, `slice()`, etc., car ces méthodes sont définies sur `Array.prototype`.
const monTableau = [1, 2, 3];
// 'push' n'est pas défini directement sur 'monTableau'
console.log(monTableau.hasOwnProperty('push')); // false
// Mais il est trouvé sur le prototype de monTableau (Array.prototype)
console.log(Object.getPrototypeOf(monTableau) === Array.prototype); // true
console.log(Array.prototype.hasOwnProperty('push')); // true
monTableau.push(4); // Fonctionne car 'push' est trouvé via la chaîne de prototypes
console.log(monTableau); // [1, 2, 3, 4]Lorsque vous utilisez une fonction constructeur avec `new`, le prototype du nouvel objet créé est automatiquement défini comme étant l'objet référencé par la propriété `prototype` de la fonction constructeur. C'est pourquoi il est efficace d'ajouter les méthodes partagées au `prototype` du constructeur : elles ne sont stockées qu'une seule fois en mémoire (sur l'objet prototype) et sont accessibles par toutes les instances via la chaîne de prototypes.
function Produit(nom, prix) {
this.nom = nom;
this.prix = prix;
}
// Ajout d'une méthode au prototype
Produit.prototype.afficher = function() {
console.log(`${this.nom} - ${this.prix}€`);
};
const p1 = new Produit("Livre", 15);
const p2 = new Produit("Stylo", 2);
p1.afficher(); // Livre - 15€
p2.afficher(); // Stylo - 2€
console.log(p1.afficher === p2.afficher); // true (la même fonction est partagée via le prototype)Le sommet de la plupart des chaînes de prototypes est `Object.prototype`. Cet objet fournit des méthodes fondamentales comme `hasOwnProperty()`, `toString()`, `valueOf()`, etc., qui sont donc disponibles (via héritage) sur presque tous les objets JavaScript.
La syntaxe `class` (ES6) : Sucre syntaxique
Pour rendre la création d'objets et l'héritage plus familiers aux développeurs venant de langages orientés objet classiques, ES6 a introduit la syntaxe `class`. Il est crucial de comprendre qu'il s'agit principalement de sucre syntaxique par-dessus le système d'héritage prototypal existant. Les classes JavaScript n'introduisent pas un nouveau modèle d'héritage.
La syntaxe `class` simplifie la définition des fonctions constructeur et l'ajout de méthodes au prototype :
class Vehicule {
// Le constructeur
constructor(marque) {
this.marque = marque;
}
// Méthode (sera ajoutée à Vehicule.prototype)
demarrer() {
console.log(`Le véhicule ${this.marque} démarre.`);
}
// Méthode statique (appartient à la classe elle-même, pas aux instances)
static info() {
console.log("Ceci est une classe de véhicule.");
}
}
const v1 = new Vehicule("Peugeot");
v1.demarrer(); // "Le véhicule Peugeot démarre."
Vehicule.info(); // "Ceci est une classe de véhicule."
// v1.info(); // TypeError: v1.info is not a functionLa syntaxe `class` facilite également l'héritage entre "classes" à l'aide des mots-clés `extends` et `super` :
class Voiture extends Vehicule { // Voiture hérite de Vehicule
constructor(marque, modele) {
super(marque); // Appelle le constructeur de la classe parente (Vehicule)
this.modele = modele;
}
// Surcharge de la méthode demarrer
demarrer() {
// Appel de la méthode parente si nécessaire
super.demarrer();
console.log(`C'est une ${this.modele}.`);
}
rouler() {
console.log(`La ${this.marque} ${this.modele} roule.`);
}
}
const maVoiture = new Voiture("Renault", "Megane");
maVoiture.demarrer();
// Output:
// Le véhicule Renault démarre.
// C'est une Megane.
maVoiture.rouler(); // "La Renault Megane roule."
// L'héritage prototypal est toujours là !
console.log(Object.getPrototypeOf(Voiture.prototype) === Vehicule.prototype); // true
console.log(Object.getPrototypeOf(maVoiture) === Voiture.prototype); // trueMême si la syntaxe `class` est plus claire et plus structurée, comprendre le fonctionnement sous-jacent des prototypes reste important pour maîtriser pleinement JavaScript et déboguer efficacement certains comportements, notamment en ce qui concerne `this` et la manipulation avancée d'objets dans Node.js.