[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/fr/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain [Back]  [Original]

Hritage et chane de prototype - JavaScript | MDN

Cette page a t traduite partir de l'anglais par la communaut. Vous pouvez contribuer en rejoignant la communaut francophone sur MDN Web Docs.

View in English Always switch to English

Hritage et chane de prototype

JavaScript peut prter confusion lorsqu'on est habitu manipuler des langages de programmation manipulant les classes (tels que Java ou C++). En effet, JavaScript est un langage dynamique et ne possde pas de concept de classe part entire (le mot-cl class a certes t ajout avec ES2015 mais il s'agit uniquement de sucre syntaxique, JavaScript continue de reposer sur l'hritage prototypique).

En ce qui concerne l'hritage, JavaScript n'utilise qu'une seule structure : les objets. Chaque objet possde une proprit prive qui contient un lien vers un autre objet appel le prototype. Ce prototype possde galement son prototype et ainsi de suite, jusqu' ce qu'un objet ait null comme prototype. Par dfinition, null ne possde pas de prototype et est ainsi le dernier maillon de la chane de prototype.

La majorit des objets JavaScript sont des instances de Object qui est l'avant dernier maillon de la chane de prototype.

Bien que cette confusion (entre classe et prototype) soit souvent avance comme l'une des faiblesses de JavaScript, le modle prototypique est plus puissant que le modle classique et il est notamment possible de construire un modle classique partir d'un modle prototypique.

Dans cet article

Hritage et chane de prototype

Proprits hrites

Les objets JavaScript sont des ensembles dynamiques de proprits (les proprits directement rattaches un objet sont appeles proprits en propre (own properties)). Les objets JavaScript possdent galement un lien vers un objet qui est leur prototype. Lorsqu'on tente d'accder aux proprits d'un objet, la proprit sera recherche d'abord sur l'objet mme, puis sur son prototype, puis sur le prototype du prototype et ainsi de suite jusqu' ce qu'elle soit trouve ou que la fin de la chane de prototype ait t atteinte.

Note : Dans la spcification ECMAScript, on utilise la notation unObjet.[[Prototype]] pour faire rfrence au prototype de unObjet. Depuis ECMAScript 2015, on peut accder [[Prototype]] grce aux accesseurs Object.getPrototypeOf() et Object.setPrototypeOf(). Cela est quivalent la proprit JavaScript __proto__ qui tait non-standard avant ES2015 mais qui tait de fait implmente par la majorit des navigateurs.

Cette proprit ne devrait pas tre confondue avec la proprit func.prototype des fonctions qui dfinissent le [[Prototype]] affecter aux instances des objets crs par cette fonction lorsqu'elle est utilise comme constructeur. La proprit Object.prototype reprsente le prototype de Object.

Voici ce qui se produit lorsqu'on tente d'accder une proprit :

js
// On commence par crer un objet o pour lequel la fonction f sera
// son constructeur et lui crera deux proprits en propre
// a et b :
let f = function () {
  this.a = 1;
  this.b = 2;
};
let o = new f(); // {a: 1, b: 2}

// on ajoute des proprits au prototype de la fonction
// f
f.prototype.b = 3;
f.prototype.c = 4;

// Note : on ne dfinit pas le prototype de f avec f.prototype = {b:3,c:4};
// car cela briserait la chane de prototype

// o.[[Prototype]] possde les proprits b and c.
// o.[[Prototype]].[[Prototype]] est Object.prototype.
// Enfin, o.[[Prototype]].[[Prototype]].[[Prototype]] vaut null.
// On a alors atteint la fin de la chane de prototype car,
// par dfinition, null n'a pas de [[Prototype]].
// Ainsi, la chane complte est ici :
// {a: 1, b: 2} ---> {b: 3, c: 4} ---> Object.prototype ---> null

console.log(o.a); // 1
// Existe-t-il une proprit 'a' en propre sur o ? Oui, elle vaut 1.

console.log(o.b); // 2
// Existe-t-il une proprit 'b' en propre sur o ? Oui, elle vaut 2.
// Le prototype possde galement une proprit 'b' mais elle n'est pas
// utilise.
// C'est ce qu'on appelle l'ombrage (shadowing en anglais)

console.log(o.c); // 4
// Existe-t-il une proprit 'c' en propre sur o ? Non, on vrifie le
// prototype.
// Existe-t-il une proprit 'c' en propre sur o.[[Prototype]] ?
// Oui, elle vaut 4.

console.log(o.d); // undefined
// Existe-t-il une proprit 'd' en propre sur o ? Non, on vrifie le
// prototype.
// Existe-t-il une proprit 'd' en propre sur o.[[Prototype]] ? Non, on vrifie le
// prototype.
// o.[[Prototype]].[[Prototype]] est Object.prototype et ne contient pas
// de proprit 'd' par dfaut. On vrifie son prototype.
// o.[[Prototype]].[[Prototype]].[[Prototype]] est null, on arrte la recherche
// aucune proprit n'est trouve, le moteur renvoie undefined.

Lorsquon dfinit une proprit sur un objet, cela dfinit une proprit en propre. La seule exception se produit lorsqu'on dfinit un accesseur et/ou un mutateur sur une proprit hrite.

Mthodes hrites

JavaScript ne possde pas de mthodes au sens des langages de classe. En effet, en JavaScript, toute fonction associe un objet est galement une proprit. Une fonction hrite se comportera comme n'importe quelle autre proprit (y compris pour l'ombrage mentionn ci-avant o on pourra parler de surcharge).

Lorsqu'une fonction hrite est excute, la valeur de this pointe vers l'objet hrit et non vers l'objet prototype qui possde la fonction comme proprit en propre.

js
var o = {
  a: 2,
  m: function () {
    return this.a + 1;
  },
};

console.log(o.m()); // 3
// Quand on appelle o.m ici, 'this' fera rfrence  o

var p = Object.create(o);
// p est un objet qui hrite de o

p.a = 4; // on cre une proprit 'a' en propre sur p
console.log(p.m()); // 5
// lorsque p.m est appele, 'this' fait rfrence  p.
// Ainsi quand p hrite de m via o,
// 'this.a' signifie p.a, soit la proprit 'a' de p

Utiliser les prototypes avec JavaScript

Regardons un peu plus en dtail ce qui se droule en arrire-plan.

Note : Pour tous les exemples suivants, nous vous invitons ouvrir la "console" de votre navigateur pour y copier/coller/diter les fragments de code. Pour savoir comment lancer cette console, vous pouvez lire la documentation des navigateurs : Firefox, Chrome, Edge.

En JavaScript, comme mentionn ci-dessus, les fonctions peuvent avoir des proprits. Toutes les fonctions ont une proprit spciale intitule prototype.

js
function faireUnTruc() {}
console.log(faireUnTruc.prototype); // Object {...}
// Peu importe comment vous dclarez la fonction.
// une fonction en JavaScript aura toujours une proprit
// prototype par dfaut.
var faireUnTruc = function () {};
console.log(faireUnTruc.prototype); // Object {...}

Comme mentionn avant, faireUnTruc() possde une proprit par dfaut prototype. Aprs avoir excut ce code dans une console, la console devrait afficher un objet semblable :

js
{
    constructor:  faireUnTruc(),
    __proto__: {
        constructor:  Object(),
        hasOwnProperty:  hasOwnProperty(),
        isPrototypeOf:  isPrototypeOf(),
        propertyIsEnumerable:  propertyIsEnumerable(),
        toLocaleString:  toLocaleString(),
        toString:  toString(),
        valueOf:  valueOf()
    }
}

On peut ajouter des proprits au prototype de faireUnTruc() comme suit :

js
function faireUnTruc() {}
faireUnTruc.prototype.toto = "truc";
console.log(faireUnTruc.prototype);

Produira le rsultat suivant :

js
{
    toto: "truc",
    constructor:  faireUnTruc(),
    __proto__: {
        constructor:  Object(),
        hasOwnProperty:  hasOwnProperty(),
        isPrototypeOf:  isPrototypeOf(),
        propertyIsEnumerable:  propertyIsEnumerable(),
        toLocaleString:  toLocaleString(),
        toString:  toString(),
        valueOf:  valueOf()
    }
}

On peut utiliser l'oprateur new afin de crer une instance de faireUnTruc() base sur ce prototype. Pour utiliser l'oprateur new, il suffira d'appeler la fonction et de prcder cet appel avec le mot-cl new. Lorsqu'on appelle une fonction avec un oprateur new, celle-ci renvoie un objet qui est une instance de la fonction. On peut ensuite ajouter des proprits sur cet objet.

Voyons le code qui suit :

js
function faireUnTruc() {}
faireUnTruc.prototype.toto = "truc"; // on ajoute une proprit au prototype
var uneInstance = new faireUnTruc();
uneInstance.prop = "une valeur"; // on ajoute une proprit sur l'objet
console.log(uneInstance);

Excut, ce code produira le rsultat suivant dans la console :

js
{
    prop: "une valeur",
    __proto__: {
        toto: "truc",
        constructor:  faireUnTruc(),
        __proto__: {
            constructor:  Object(),
            hasOwnProperty:  hasOwnProperty(),
            isPrototypeOf:  isPrototypeOf(),
            propertyIsEnumerable:  propertyIsEnumerable(),
            toLocaleString:  toLocaleString(),
            toString:  toString(),
            valueOf:  valueOf()
        }
    }
}

Comme nous l'avons vu avant, la valeur de __proto__ pour uneInstance est faireUnTruc.prototype. Mais quel est l'intrt ? Lorsqu'on accde une proprit de uneInstance, le moteur contrle d'abord si uneInstance possde cette proprit.

Si uneInstance ne possde pas cette proprit, le moteur contrlera la proprit sur la proprit __proto__ de uneInstance (c'est--dire faireUnTruc.prototype). Si la proprit __proto__ de uneInstance possde la proprit qu'on recherche, ce sera celle-ci qui sera utilise.

Si __proto__ de unTruc ne possde pas la proprit recherche, le moteur contrle la proprit __proto__ de la proprit __proto__ de uneInstance. Par dfaut, la proprit __proto__ de n'importe quelle proprit prototype d'une fonction est window.Object.prototype. Ainsi, la proprit __proto__ de la proprit __proto__ de uneInstance (c'est--dire __proto__ de faireUnTruc.prototype (c'est--dire. Object.prototype)) est contrle pour vrifier si la proprit y est prsente.

Si la proprit n'est pas trouve sur la proprit __proto__ de la proprit __proto__ de uneInstance, c'est la proprit __proto__ de la proprit __proto__ de la proprit __proto__ de uneInstance qui est contrle. Cependant il y a un problme car la proprit __proto__ de la proprit __proto__ de la proprit __proto__ de unTruc n'existe pas. Autrement dit, toute la chane de prototype a t parcouru et on ne peut pas remonter d'un cran sur un autre __proto__ et le moteur peut conclure que la proprit n'existe pas pour cet objet et renvoyer undefined.

Regardons ce qui se produit dans la console avec un peu de code :

js
function faireUnTruc() {}
faireUnTruc.prototype.toto = "truc";
var uneInstance = new faireUnTruc();
uneInstance.prop = "une valeur";
console.log("uneInstance.prop: " + uneInstance.prop);
console.log("uneInstance.toto: " + uneInstance.toto);
console.log("faireUnTruc.prop: " + faireUnTruc.prop);
console.log("faireUnTruc.toto: " + faireUnTruc.toto);
console.log("faireUnTruc.prototype.prop: " + faireUnTruc.prototype.prop);
console.log("faireUnTruc.prototype.toto:  " + faireUnTruc.prototype.toto);

Le rsultat est le suivant :

js
uneInstance.prop: une valeur
uneInstance.toto: truc
faireUnTruc.prop: undefined
faireUnTruc.toto: undefined
faireUnTruc.prototype.prop: undefined
faireUnTruc.prototype.toto: truc

Les diffrentes faons de crer des objets et les impacts sur la chane de prototype

Objets crs avec les raccourcis syntaxiques (littraux)

js
var o = { a: 1 };

// Le nouvel objet possde Object.prototype comme [[Prototype]]
// o ne possde pas de proprit 'hasOwnProperty' en propre
// hasOwnProperty est une proprit en propre de Object.prototype.
// o hrite de hasOwnProperty via Object.prototype
// Object.prototype possde null comme prototype.
// o ---> Object.prototype ---> null

var b = ["coucou", "a va", "?"];

// Les tableaux (Array) hritent de Array.prototype
// (qui possde les mthodes indexOf, forEach, etc.)
// La chane de prototype est donc :
// b ---> Array.prototype ---> Object.prototype ---> null

function f() {
  return 2;
}

// Les fonctions hritent de Function.prototype
// (qui possde les mthodes call, bind, etc.)
// La chane de prototype est donc
// f ---> Function.prototype ---> Object.prototype ---> null

Objets crs avec un constructeur

En JavaScript, un constructeur est juste une fonction que l'on invoque avec l'oprateur new.

js
function Graphe() {
  this.sommets = [];
  this.artes = [];
}

Graphe.prototype = {
  ajoutSommet: function (v) {
    this.sommets.push(v);
  },
};

var g = new Graphe();
// g est un objet qui possde les proprits 'sommets' and 'artes' en propre.
// g.[[Prototype]] est la valeur de Graphe.prototype lorsque "new Graphe()" est excut.

Objets crs avec Object.create()

ECMAScript 5 a introduit une nouvelle mthode : Object.create(). Appeler cette mthode cre un nouvel objet et le prototype de cet objet est le premier argument de cette fonction :

js
var a = { a: 1 };
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (hrite)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty);
// undefined, car d n'hrite pas de Object.prototype

Suppression des proprits avec delete

L'oprateur delete permet de supprimer une proprit directement rattache un objet. En revanche, il n'empchera pas l'exploration de la chane de prototype :

js
let a = { toto: 1 };
let b = Object.create(a);

console.log(b.toto); // Affiche 1 car c'est une proprit disponible via le prototype
b.toto = 5;
console.log(b.toto); // Affiche 5, dsormais cette proprit existe sur l'objet

delete b.toto;
console.log(b.toto); // Affiche 1 : la proprit n'est plus disponible sur l'objet mais
// on peut toujours la rcuprer via le prototype

Objets crs avec le mot-cl class

ECMAScript 2015 introduit plusieurs mots-cls destins crer du sucre syntaxique pour manipuler des classes. Ces mots-cls sont class, constructor, static, extends et super.

js
"use strict";

class Polygone {
  constructor(hauteur, largeur) {
    this.hauteur = hauteur;
    this.largeur = largeur;
  }
}

class Carr extends Polygone {
  constructor(longueurCt) {
    super(longueurCt, longueurCt);
  }
  get aire() {
    return this.hauteur * this.largeur;
  }
  set longueurCt(nouvelleLongueur) {
    this.hauteur = nouvelleLongueur;
    this.largeur = nouvelleLongueur;
  }
}

var carr = new Carr(2);

Performance

Le temps de recherche des proprits sera plus lev si ces proprits sont situes plus loin dans la chane de prototype. Tenter d'accder ces proprits loignes pourra avoir un impact ngatif sur les performances. De plus, tenter d'accder des proprits inexistantes entranera toujours le parcours de l'ensemble de la chane de prototype.

Lorsqu'on parcourt les proprits d'un objet, toutes les proprits numrables situes sur la chane de prototype seront parcourues. Pour vrifier si un objet possde une proprit en propre plus que via sa chane de prototype, on devra utiliser la mthode hasOwnProperty() qui est hrite grce Object.prototype. Prenons un exemple concret avec le cas du graphe trait dans un exemple prcdent :

js
console.log(g.hasOwnProperty("artes"));
// true

console.log(g.hasOwnProperty("nononon"));
// false

console.log(g.hasOwnProperty("ajoutSommet"));
// false

console.log(g.__proto__.hasOwnProperty("ajoutSommet"));
// true

Note : Tester si une proprit vaut undefined ne suffit pas vrifier la prsence de la proprit sur un objet : une proprit peut trs bien exister sur un objet mais valoir undefined.

Mauvaise pratique : tendre les prototypes natifs

On peut parfois voir du code qui tend Object.prototype ou l'un des prototypes natifs.

Cette technique est intitule monkey patching et brise l'encapsulation. Bien qu'elle soit utilise par certains frameworks, il n'existe pas de raison suffisante pour tendre les objets natifs avec des fonctionnalits non-standard.

La seule raison qui peut prvaloir pour l'extension de prototypes natifs est l'ajout de fonctionnalits JavaScript apparues avec les nouvelles versions des spcifications et moteurs afin d'en disposer dans de plus anciens environnements.

Rsum des mthodes pour tendre la chane de prototype

Voici un tableau avec les quatre outils qui permettent d'tendre une chane de prototypes avec chacun leurs avantages et leurs inconvnients. Tous les exemples mentionns permettent de crer le mme objet inst (et affichant donc le mme rsultat dans la console) mais de faon diffrente.

Nom Exemples Avantages Inconvnients
Initialisation
js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto = new toto;
proto.truc_prop = "truc val";
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop);
Prise en charge par l'ensemble des navigateurs. Cette mthode est trs rapide, standard et facilement optimisable.

Afin d'utiliser cette mthode, il faut que la fonction ait t initialise. Pendant cette initialisation, le constructeur peut enregistrer des informations uniques qui doivent tre gnres pour chaque objet.

Toutefois, il est possible que ces informations uniques ne soient gnres qu'une seule fois.

De plus, l'initialisation du constructeur peut ajouter des mthodes non souhaites sur l'objet.

Cela dit, ces problmes ne se rvlent que rarement.

Object.create()
js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto = Object.create(
  toto.prototype
);
proto.truc_prop = "truc val";
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop);
js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto = Object.create(
  toto.prototype,
  {
    truc_prop: {
      value: "truc val"
    }
  }
);
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop)
Prise en charge par la majorit des navigateurs actuels. Elle permet de dfinir directement __proto__ en une seule fois et le navigateur peut mieux optimiser l'objet. Elle permet aussi de crer des objets sans prototype avec Object.create(null).

Cette mthode n'est pas prise en charge par IE8 et les versions antrieures. Toutefois, Microsoft ayant mis un terme au support des systmes qui utilisent ces navigateurs, ce ne devrait pas tre un problme pour la plupart des navigation.

De plus, la lenteur de l'initialisation de l'objet peut tre causer des soucis de performances lorsqu'on utilise un deuxime argument car descripteur de proprit possde un objet rattach. Lorsqu'on gre des centaines de milliers de descripteurs, cela peut entraner un certain lag.

Object.setPrototypeOf()

js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto = {
  truc_prop: "truc val"
};
Object.setPrototypeOf(
  proto, toto.prototype
);
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop);
js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto;
proto=Object.setPrototypeOf(
  { truc_prop: "truc val" },
  toto.prototype
);
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop)
Prise en charge par l'ensemble des navigateurs actuels. Elle permet de manipuler dynamiquement le prototype d'un objet et galement de rattacher un prototype un objet qui n'aurait pas de prototype. Cette mthode devrait tre dprcie et possde des performances faibles. En effet, les moteurs tenteront d'optimiser la connaissance de la structure du prototype et cette mthode viendra l'enconte de ces hypothses et certains navigateurs pourront mme recompiler le code pour le faire fonctionner selon les spcifications. Cette mthode n'est pas prise en charge par IE8 et les versions antrieures.
__proto__
js
function toto(){}
toto.prototype = {
  toto_prop: "toto val"
};
function truc(){}
var proto = {
  truc_prop: "truc val",
  __proto__: toto.prototype
};
truc.prototype = proto;
var inst = new truc();
console.log(inst.toto_prop);
console.log(inst.truc_prop);
js
var inst = {
  __proto__: {
    truc_prop: "truc val",
    __proto__: {
      toto_prop: "toto val",
      __proto__: Object.prototype
    }
  }
};
console.log(inst.toto_prop);
console.log(inst.truc_prop)
Prise en charge par l'ensemble des navigateurs actuels (y compris IE11 et ultrieurs). Dfiinir __proto__ sur quelque chose qui n'est pas un objet chouera silencieusement. Cette mthode est dprcie et n'est pas performante car les moteurs tentent d'optimiser les prototypes. Aussi, le modifier ainsi dynamiquement bloque ces optimisations et peut causer la recompilation du code pour qu'il fonctionne selon les spcifications. Cette mthode n'est pas prise en charge par IE10 et les versions antrieures.

prototype et Object.getPrototypeOf()

JavaScript peut prter confusion pour les dveloppeurs utilisant Java ou C++. JavaScript est un langage dynamique et les structures peuvent voluer lors de l'excution.

Vous avez peut-tre remarqu que la fonction A possde une proprit spciale intitule prototype. Cette proprit spciale fonctionne avec l'oprateur new. Elle permet de copier la rfrence l'objet prototype sur la proprit interne [[Prototype]] de la nouvelle instance cre. Ainsi, avec var a1 = new A(), le moteur JavaScript dfinira a1.[[Prototype]] = A.prototype. Quand on tente d'accder une des proprits de l'instance, JavaScript vrifie la prsence sur l'instance puis analyse son prototype [[Prototype]]. Cela signifie que tout ce qui est dfini sur prototype est effectivement partag par l'ensemble des instances et on peut mme modifier prototype en cours de route afin de modifier indirectement l'ensemble des instances.

Dans l'exemple prcdent, si on avait eu var a1 = new A(); var a2 = new A(); alors a1.faireUnTruc aurait fait rfrence Object.getPrototypeOf(a1).faireUntruc qui est identique A.prototype.faireUnTruc. Autrement dit Object.getPrototypeOf(a1).faireUnTruc == Object.getPrototypeOf(a2).faireUnTruc == A.prototype.faireUnTruc.

Autrement dit prototype peut tre utilis pour les types et Object.getPrototypeOf() pour les instances.

[[Prototype]] est analys de faon rcursive. Ainsi, a1.faireUnTruc correspondra chercher Object.getPrototypeOf(a1).faireUnTruc puis Object.getPrototypeOf(Object.getPrototypeOf(a1)).faireUnTruc etc., jusqu' ce qu'elle soit trouve ou que Object.getPrototypeOf renvoie null.

Ainsi, quand on appelle :

js
var o = new Toto();

Le moteur JavaScript effectue les tapes suivantes :

js
var o = new Object();
o.[[Prototype]] = Toto.prototype;
Toto.call(o);

(ou quelque chose qui y ressemble) et si on crit ensuite :

js
o.uneProprit;

Le moteur vrifie si o possde une proprit uneProprit en propre. Si ce n'est pas le cas, il vrifie Object.getPrototypeOf(o).uneProprit et ainsi de suite.

Conclusion

Il est essentiel de comprendre le modle d'hritage prototypique avant d'crire du code complexe qui repose sur ces notions. Il est galement prfrable d'avoir une ide de la longueur de la chane de prototype utilise pour les diffrents objets et de fragmenter cette chane si besoin afin d'viter des cueils de performances. Enfin, on veillera ne pas tendre les prototypes natifs sauf afin d'muler des nouvelles fonctionnalits qui ne seraient pas disponibles dans l'environnement utilis.


Web Proxy Viewer  |  New URL  |  Original Page