Un tableau vide ne contient rien, et pourtant il sait trier, filtrer, se transformer. Un objet créé avec deux propriétés répond à des méthodes que personne n'a écrites pour lui. Ces capacités ne sont pas copiées : elles sont empruntées.
Le mécanisme s'appelle le prototype, et c'est la seule forme de partage de comportement du langage, y compris derrière le mot-clé class.
Définition
Le prototype d'un objet est un autre objet auquel il délègue les recherches. Quand vous lisez une propriété absente, le moteur la cherche sur ce prototype, puis sur le prototype de celui-ci, jusqu'à tomber sur null.
const liste = [1, 2, 3];
console.log(Object.getPrototypeOf(liste) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
console.log(liste.hasOwnProperty("length")); // true, propriété propre
console.log("map" in liste); // true, trouvée plus hautLe tableau ne possède que ses éléments et sa longueur. map et les autres vivent une fois pour toutes sur Array.prototype, partagées par tous les tableaux du programme.
prototype et le prototype, deux choses différentes
La confusion vient d'un mot employé pour deux réalités. Ce tableau à trois colonnes les sépare.
| Écriture | Porté par | Ce que c'est |
|---|---|---|
Produit.prototype | Une fonction constructrice | L'objet que recevront les instances |
Object.getPrototypeOf(p) | N'importe quel objet | Le prototype dont il hérite |
p.__proto__ | N'importe quel objet | La même chose, écriture historique |
Seules les fonctions portent une propriété prototype. Elle ne sert qu'au moment où la fonction est appelée avec new.
function Produit(nom) {
this.nom = nom;
}
Produit.prototype.etiquette = function () {
return "Produit : " + this.nom;
};
const p = new Produit("Clavier");
console.log(p.etiquette()); // Produit : Clavier
console.log(p.hasOwnProperty("etiquette")); // false, elle est sur le prototypeCe que ça change à l'usage
- Une méthode existe en un seul exemplaire. Mille instances partagent la même fonction, pas mille copies.
- instanceof interroge la chaîne. Il ne compare pas des types, il remonte les prototypes.
- Une class ne change rien à tout ça. Elle écrit la même structure avec une syntaxe plus lisible.
Modifier un prototype natif, par exemple ajouter une méthode à Array.prototype, est une mauvaise idée : le nom choisi peut heurter un futur ajout du langage, et le comportement change partout.
Questions fréquentes
Pourquoi __proto__ est-il déconseillé ?
Parce que c'est un accès historique, conservé pour ne pas casser le web existant, et qu'il est modifiable. Changer le prototype d'un objet déjà utilisé dégrade fortement les optimisations du moteur. Préférez Object.getPrototypeOf() en lecture, et Object.create() à la création.
Comment créer un objet sans aucun prototype ?
Avec Object.create(null). L'objet obtenu n'hérite de rien, pas même de toString. C'est utile pour un dictionnaire alimenté par des données extérieures, où une clé nommée constructor ne doit pas heurter une propriété héritée.
La délégation coûte-t-elle du temps à chaque lecture ?
La recherche remonte la chaîne, donc une propriété située trois niveaux plus haut demande trois étapes. Les moteurs mettent ces chemins en cache et le coût reste invisible. Notre formation JavaScript détaille cette chaîne avant d'aborder les classes, dans cet ordre précisément.