Création et Manipulation d’Objets en JavaScript

Points clés :

La chaîne de prototypes :

object
├── properties
│   └── ...
└── *prototype*
    ├── properties
    │   └── ...
    └── *prototype*
        └── ...

Exemple :

  const o = {
      'a': 0,
      'b': false
  };
  
  Object.prototype.ok = function() {
      return 'C\'est OK !';
  };
  
  Object.prototype.not_ok = 'Pas OK.';
  
  console.log(o.ok()); // 'C'est OK !'
  console.log(o.not_ok); // 'Pas OK.'

Cela montre comment les prototypes permettent aux objets d’accéder à des propriétés et méthodes qui ne sont pas directement définies sur eux.

Approches pour créer un objet en JavaScript

Objet littéral ({})

📌 L’approche la plus simple et intuitive, mais pas idéale pour des objets partageant des méthodes.

const book = {
  title: "1984",
  author: "George Orwell",
  read() {
    console.log(`Vous lisez "${this.title}" de ${this.author}.`);
  }
};

book.read(); // Vous lisez "1984" de George Orwell.

✅ Simple et lisible.
❌ Pas de partage efficace des méthodes : chaque instance duplique les fonctions.


Object.create(), prototypes et champs cachés

La fonctionnalité la plus importante de Object.create est de permettre de définir des objets sécurisés. Les valeurs peuvent être en lecture seule ou protégées par des accesseurs… Les attributs ne peuvent pas vraiment être privés, mais ils peuvent être cachés.

Exemple :

var obj = Object.create(null); // Objet sans lien de prototype

var obj2 = Object.create(Object.prototype); // équivalent à "var obj2 = {};"

var positivePoint = Object.create(Object.prototype, {
  x: {
    get: function() { return this._x || 0; },
    set: function(value) {
      if (value < 0) {
        console.log("Erreur ! Ce point doit avoir des valeurs positives", this.y);
      } else {
        this._x = value;
      }
    },
  },
  y: {
    get: function() { return this._y || 0; },
    set: function(value) {
      if (value < 0) {
        console.log("Erreur ! Ce point doit avoir des valeurs positives");
      } else {
        this._y = value;
      }
    },
  },
  toString: {
    value: function() {
      return '(' + this.x + ', ' + this.y + ')';
    },
  }
});

Utilisation :

positivePoint.x = 10;  // Définit x à 10
positivePoint.y = -5;  // Affiche l'erreur, car y ne peut pas être négatif
positivePoint.y = 5;   // Définit y à 5

console.log(positivePoint.toString()); // (10, 5)

Cela montre comment Object.create permet de créer des objets sécurisés en contrôlant l’accès à leurs propriétés et en cachant des données internes.

✅ Créer des objets sécurisés en contrôlant l’accès à leurs propriétés et en cachant des données internes.
✅ Bonne maîtrise de l’héritage sans utiliser class.
❌ Moins intuitif que class et peu utilisé en pratique.


Fonction Constructeur (new + Prototype)

📌 Une méthode classique pour créer plusieurs objets partageant des méthodes.

🔹 Exemple : Une gestion d’inventaire

function Product(name, price) {
  this.name = name;
  this.price = price;
}

// Ajout d'une méthode partagée via le prototype
Product.prototype.displayInfo = function () {
  console.log(`${this.name} coûte ${this.price}€`);
};

const laptop = new Product("MacBook", 2500);
const phone = new Product("iPhone", 1200);

laptop.displayInfo(); // MacBook coûte 2500€
phone.displayInfo();  // iPhone coûte 1200€

console.log(laptop.__proto__ === Product.prototype); // true

✅ Plus performant que l’objet littéral grâce au partage de méthodes via prototype.
❌ Syntaxe plus lourde et remplacée par class en ES6.


Class class en ES6 (Design Pattern Class + Champs Privés)

📌 Le class en ES6 simplifie l’approche objet avec une syntaxe plus intuitive.

Exemple : Gestion d’utilisateurs avec champs privés (#)

class User {
  #password; // Champ privé

  constructor(username, password) {
    this.username = username;
    this.#password = password;
  }

  checkPassword(input) {
    return this.#password === input;
  }

  get usernameInfo() {
    return `Utilisateur: ${this.username}`;
  }
}

const user = new User("Alice", "supersecret");
console.log(user.username); // Alice
console.log(user.checkPassword("supersecret")); // true
console.log(user.#password); // Erreur ! (Champ privé)

✅ Plus lisible et proche d’autres langages orientés objet.
✅ Possibilité d’avoir des champs privés (#password).
Attention, sous le capot, cela utilise toujours le mécanisme de prototype !


Héritage grâce à la Chaîne de Prototypes

📌 Comment JavaScript cherche une méthode ou propriété dans la chaîne de prototypes ?

Exemple :

class Animal {
  constructor(name) {
    this.name = name;
  }
  
  speak() {
    console.log(`${this.name} fait un bruit.`);
  }
}

class Dog extends Animal {
  speak() {
    console.log(`${this.name} aboie.`);
  }
}

const myDog = new Dog("Rex");

myDog.speak(); // Rex aboie.
console.log(myDog.hasOwnProperty("speak")); // false (il est sur le prototype)
console.log(Object.getPrototypeOf(myDog) === Dog.prototype); // true
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
  myDog
  ├── propriétés
  │   ├── name: String
  └── *prototype* (Dog.prototype)
      ├── propriétés
      │   └── speak(): [Fonction]
      └── *prototype* (Animal.prototype)
            ├── propriétés
            │   └── speak(): [Fonction]
            └── *prototype* (Object.prototype) 
                    ├── propriétés
                    │   └── hasOwnProperty(): [Fonction]
                    └── *prototype* (null)

Si une méthode n’est pas trouvée, JavaScript remonte la chaîne jusqu’à Object.prototype.
Si elle n’existe pas, il retourne undefined.
Attention : Si une méthode speak() existe dans Dog.prototype, celle de Animal.prototype est ignorée.

Héritage Prototypal avec Object.create()

const mammal = {
  breathe() {
    console.log("Respire...");
  }
};

const cat = Object.create(mammal, {
  breathe: {
    value: function() {
      console.log("Le chat respire doucement...");
    }
  },
  meow: {
    value: function() {
      console.log("Miaou !");
    }
  }
});

cat.breathe();  // Le chat respire doucement...
cat.meow();     // Miaou !

✅ Permet de créer un objet sans class avec une chaîne d’héritage explicite.


Héritage avec extends

class Employee {
  constructor(name, salary) {
    this.name = name;
    this.salary = salary;
  }
  
  showInfo() {
    console.log(`${this.name} gagne ${this.salary}€.`);
  }
}

class Manager extends Employee {
  constructor(name, salary, department) {
    super(name, salary);
    this.department = department;
  }

  showInfo() {
    console.log(`${this.name}, manager du département ${this.department}, gagne ${this.salary}€.`);
  }
}

const boss = new Manager("Sophie", 80000, "IT");
boss.showInfo(); // Sophie, manager du département IT, gagne 80000€.

super() permet d’appeler le constructeur parent.
Plus lisible que Object.create(), recommandé pour du code moderne.


Conclusion

🔹 Comprendre le fonctionnement des objets et prototypes est essentiel pour bien gérer les modèles objets.
🔹 Les classes en ES6 facilitent la programmation objet, mais sous le capot, tout repose sur les prototypes.
🔹 Les prototypes permettent un héritage puissant, mais ce mécanisme est différents des schémas d’héritage classiques en génie logiciel (java)