La propriĂ©tĂ© "prototype" est largement utilisĂ©e au centre de JavaScript lui-mĂȘme. Toutes les fonctions constructeurs intĂ©grĂ©es lâutilisent.
Nous verrons dâabord les dĂ©tails, puis comment lâutiliser pour ajouter de nouvelles fonctionnalitĂ©s aux objets intĂ©grĂ©s.
Object.prototype
Disons que nous produisons un objet vide :
let obj = {};
alert( obj ); // "[object Object]" ?
OĂč est le code qui gĂ©nĂšre la chaĂźne "[object Object]" ? Câest une mĂ©thode toString intĂ©grĂ©e, mais oĂč est-elle ? Lâobjet obj est vide !
âŠMais la notation abrĂ©gĂ©e obj = {} est identique Ă obj = new Object(), oĂč Object est une fonction constructeur de lâobjet intĂ©grĂ©e, avec son propre prototype rĂ©fĂ©rençant un Ă©norme objet avec toString et dâautres mĂ©thodes.
Voici ce qui se passe :
Lorsque new Object() est appelé (ou un objet littéral {...} est créé), le [[Prototype]] de celui-ci est défini sur Object.prototype conformément à la rÚgle dont nous avons parlé dans le chapitre précédent :
Ainsi, quand on appelle obj.toString(), la méthode est extraite de Object.prototype.
Nous pouvons le vérifier comme ceci :
let obj = {};
alert(obj.__proto__ === Object.prototype); // true
alert(obj.toString === obj.__proto__.toString); //true
alert(obj.toString === Object.prototype.toString); //true
Veuillez noter quâil nây a plus de [[Prototype]] dans la chaĂźne au dessus de Object.prototype :
alert(Object.prototype.__proto__); // null
Autres prototypes intégrés
Dâautres objets intĂ©grĂ©s, tels que Array, Date, Function et autres, conservent Ă©galement des mĂ©thodes dans des prototypes.
Par exemple, lorsque nous crĂ©ons un tableau [1, 2, 3], le constructeur new Array() par dĂ©faut est utilisĂ© en interne. Donc Array.prototype devient son prototype et fournit des mĂ©thodes. Câest trĂšs efficace en mĂ©moire.
Par spĂ©cification, tous les prototypes intĂ©grĂ©s ont Object.prototype en haut. Câest pourquoi certaines personnes disent que âtout hĂ©rite dâobjetsâ.
Voici la vue dâensemble :
Vérifions les prototypes manuellement :
let arr = [1, 2, 3];
// il hérite de Array.prototype ?
alert( arr.__proto__ === Array.prototype ); // true
// puis de Object.prototype ?
alert( arr.__proto__.__proto__ === Object.prototype ); // true
// et null tout en haut.
alert( arr.__proto__.__proto__.__proto__ ); // null
Certaines méthodes dans les prototypes peuvent se chevaucher, par exemple, Array.prototype a son propre toString qui répertorie les éléments délimités par des virgules :
let arr = [1, 2, 3]
alert(arr); // 1,2,3 <-- le résultat de Array.prototype.toString
Comme nous lâavons vu prĂ©cĂ©demment, Object.prototype a aussi toString, mais Array.prototype est plus proche dans la chaĂźne, la variante de tableau est donc utilisĂ©e.
Les outils intĂ©grĂ©s au navigateur, tels que la console de dĂ©veloppement Chrome, affichent Ă©galement lâhĂ©ritage (il faut Ă©ventuellement utiliser console.dir pour les objets intĂ©grĂ©s) :
Les autres objets intĂ©grĂ©s fonctionnent Ă©galement de la mĂȘme maniĂšre. MĂȘme les fonctions â ce sont des objets dâun constructeur intĂ©grĂ© Function, et leurs mĂ©thodes (call/apply et autres) sont extraites de Function.prototype. Les fonctions ont aussi leur propre toString.
function f() {}
alert(f.__proto__ == Function.prototype); // true
alert(f.__proto__.__proto__ == Object.prototype); // true, hérite d'objets
Primitives
Une chose complexe se produit avec les chaßnes, les nombres et les booléens.
Comme on sâen souvient, ce ne sont pas des objets. Mais si nous essayons dâaccĂ©der Ă leurs propriĂ©tĂ©s, des objets wrapper temporaires sont créés Ă lâaide des constructeurs intĂ©grĂ©s String, Number et Boolean, ils fournissent les mĂ©thodes et disparaissent.
Ces objets sont créés de maniÚre invisible pour nous et la plupart des moteurs les optimisent, mais la spécification le décrit exactement de cette façon. Les méthodes de ces objets résident également dans des prototypes, disponibles sous les noms String.prototype, Number.prototype et Boolean.prototype.
null et undefined nâont pas de wrappers dâobjetLes valeurs spĂ©ciales null et undefined se dĂ©marquent. Elles nâont pas de wrapper dâobjet, donc les mĂ©thodes et les propriĂ©tĂ©s ne sont pas disponibles pour eux. Et il nây a pas non plus de prototypes correspondants.
Modification des prototypes natifs
Les prototypes natifs peuvent ĂȘtre modifiĂ©s. Par exemple, si nous ajoutons une mĂ©thode Ă String.prototype, elle devient disponible pour toutes les chaĂźnes :
String.prototype.show = function() {
alert(this);
};
"BOOM!".show(); // BOOM!
Au cours du processus de dĂ©veloppement, nous pouvons avoir des idĂ©es de nouvelles mĂ©thodes intĂ©grĂ©es que nous aimerions avoir et nous pourrions ĂȘtre tentĂ©s de les ajouter Ă des prototypes natifs. Mais câest gĂ©nĂ©ralement une mauvaise idĂ©e.
Les prototypes sont globaux, il est donc facile de crĂ©er un conflit. Si deux bibliothĂšques ajoutent une mĂ©thode String.prototype.show, lâune dâelles remplacera la mĂ©thode de lâautre.
Donc, généralement, modifier un prototype natif est considéré comme une mauvaise idée.
Dans la programmation moderne, il nây a quâun seul cas oĂč la modification de prototypes natifs est approuvĂ©e. Le polyfilling.
Polyfilling est un terme utilisĂ© pour remplacer une mĂ©thode existante dans la spĂ©cification JavaScript, mais qui nâest pas encore prise en charge par un moteur JavaScript particulier.
Ensuite, nous pouvons lâimplĂ©menter manuellement et y ajouter le prototype intĂ©grĂ©.
Par exemple :
if (!String.prototype.repeat) { // s'il n'y a pas une telle méthode
// ajouter le au prototype
String.prototype.repeat = function(n) {
// répéter la chaßne n fois
// en fait, le code devrait ĂȘtre un peu plus complexe que cela
// (l'algorithme complet est dans la spécification)
// mais mĂȘme un polyfill imparfait est souvent considĂ©rĂ© comme suffisant
return new Array(n + 1).join(this);
};
}
alert( "La".repeat(3) ); // LaLaLa
Emprunt de prototypes
Dans le chapitre DĂ©corateurs et transferts, call/apply nous avons parlĂ© de lâemprunt de mĂ©thode.
Câest quand nous prenons une mĂ©thode dâun objet et le copions dans un autre.
Certaines méthodes de prototypes natifs sont souvent empruntées.
Par exemple, si nous crĂ©ons un objet semblable Ă un tableau, nous voudrons peut-ĂȘtre y copier des mĂ©thodes Array.
Par exemple :
let obj = {
0: "Hello",
1: "world!",
length: 2,
};
obj.join = Array.prototype.join;
alert( obj.join(',') ); // Hello,world!
Cela fonctionne car lâalgorithme interne de la mĂ©thode join intĂ©grĂ©e ne se prĂ©occupe que des index corrects et de la propriĂ©tĂ© length. Il ne vĂ©rifie pas que lâobjet est bien un tableau. Et beaucoup de mĂ©thodes intĂ©grĂ©es sont comme ça.
Une autre possibilité consiste à hériter en fixant obj.__proto__ sur Array.prototype, afin que toutes les méthodes Array soient automatiquement disponibles dans obj.
Mais câest impossible si obj hĂ©rite dĂ©jĂ dâun autre objet. Nâoubliez pas que nous ne pouvons hĂ©riter que dâun objet Ă la fois.
Lâemprunt des mĂ©thodes est flexible, cela permet de mĂ©langer les fonctionnalitĂ©s provenants dâobjets diffĂ©rents en cas de besoin.
Résumé
- Tous les objets intĂ©grĂ©s suivent le mĂȘme schĂ©ma :
- Les méthodes sont stockées dans le prototype (
Array.prototype,Object.prototype,Date.prototype, etc.). - Lâobjet lui-mĂȘme ne stocke que les donnĂ©es (Ă©lĂ©ments de tableau, propriĂ©tĂ©s de lâobjet, date).
- Les méthodes sont stockées dans le prototype (
- Les primitives stockent Ă©galement des mĂ©thodes dans des prototypes dâobjets wrapper :
Number.prototype,String.prototype,Boolean.prototype. Seulsundefinedetnullnâont pas dâobjets wrapper. - Les prototypes intĂ©grĂ©s peuvent ĂȘtre modifiĂ©s ou remplis avec de nouvelles mĂ©thodes. Mais il nâest pas recommandĂ© de les changer. La seule cause possible est probablement lâajout dâun nouveau standard, mais pas encore pris en charge par le moteur JavaScript.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)