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â¦)