La propriété "prototype"
est largement utilisée par le cœur de JavaScript lui-même. Toutes les fonctions de constructeur intégrées l'utilisent.
Nous examinerons d'abord les détails, puis comment l'utiliser pour ajouter de nouvelles fonctionnalités aux objets intégrés.
Disons que nous générons un objet vide :
soit obj = {} ; alerte( obj ); // "[objet Objet]" ?
Où est le code qui génère la chaîne "[object Object]"
? C'est une méthode toString
intégrée, mais où se trouve-t-elle ? L' obj
est vide !
…Mais la notation courte obj = {}
est la même que obj = new Object()
, où Object
est une fonction de constructeur d'objet intégrée, avec son propre prototype
faisant référence à un objet énorme avec toString
et d'autres méthodes.
Voici ce qui se passe :
Lorsque new Object()
est appelé (ou qu'un objet littéral {...}
est créé), son [[Prototype]]
est défini sur Object.prototype
selon la règle dont nous avons parlé dans le chapitre précédent :
Ainsi, lorsque obj.toString()
est appelé, la méthode est extraite de Object.prototype
.
Nous pouvons le vérifier comme ceci :
soit obj = {} ; alert(obj.__proto__ === Object.prototype); // vrai alert(obj.toString === obj.__proto__.toString); //vrai alert(obj.toString === Object.prototype.toString); //vrai
Veuillez noter qu'il n'y a plus de [[Prototype]]
dans la chaîne au dessus de Object.prototype
:
alert(Object.prototype.__proto__); // nul
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. Array.prototype
devient donc son prototype et fournit des méthodes. C'est très efficace en termes de mémoire.
Par spécification, tous les prototypes intégrés ont Object.prototype
en haut. C'est pourquoi certains disent que « tout hérite des objets ».
Voici l'image globale (pour 3 éléments intégrés) :
Vérifions les prototypes manuellement :
soit arr = [1, 2, 3]; // il hérite de Array.prototype ? alert( arr.__proto__ === Array.prototype ); // vrai // puis depuis Object.prototype ? alert( arr.__proto__.__proto__ === Object.prototype ); // vrai // et null en haut. alerte( arr.__proto__.__proto__.__proto__ ); // nul
Certaines méthodes des prototypes peuvent se chevaucher, par exemple, Array.prototype
possède son propre toString
qui répertorie les éléments délimités par des virgules :
soit arr = [1, 2, 3] alerte(arr); // 1,2,3 <-- le résultat de Array.prototype.toString
Comme nous l'avons vu précédemment, Object.prototype
doit également toString
, mais Array.prototype
est plus proche dans la chaîne, donc la variante array est utilisée.
Les outils intégrés au navigateur tels que la console de développement Chrome affichent également l'héritage ( console.dir
peut devoir être utilisé pour les objets intégrés) :
D'autres objets intégrés fonctionnent également de la même manière. Même les fonctions – ce sont des objets d’un constructeur Function
intégré, et leurs méthodes ( call
/ apply
et autres) sont extraites de Function.prototype
. Les fonctions ont également leur propre toString
.
fonction f() {} alert(f.__proto__ == Function.prototype); // vrai alert(f.__proto__.__proto__ == Object.prototype); // vrai, hérite des objets
La chose la plus complexe se produit avec les chaînes, les nombres et les booléens.
On s'en souvient, ce ne sont pas des objets. Mais si nous essayons d'accéder à leurs propriétés, les 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
.
Les valeurs null
et undefined
n'ont pas de wrappers d'objet
Les valeurs spéciales null
et undefined
se distinguent. Ils n'ont pas de wrappers d'objet, donc les méthodes et propriétés ne sont pas disponibles pour eux. Et il n’existe pas non plus de prototypes correspondants.
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 = fonction() { alerte(ce); } ; "BOOM!".show(); // BOUM !
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 pouvons être tentés de les ajouter aux prototypes natifs. Mais c'est généralement une mauvaise idée.
Important:
Les prototypes sont mondiaux, il est donc facile de créer un conflit. Si deux bibliothèques ajoutent une méthode String.prototype.show
, alors l'une d'elles écrasera la méthode de l'autre.
Ainsi, généralement, modifier un prototype natif est considéré comme une mauvaise idée.
Dans la programmation moderne, il n’existe qu’un seul cas où la modification des prototypes natifs est approuvée. C'est du polyfilling.
Le polyfilling est un terme permettant de remplacer une méthode qui existe dans la spécification JavaScript, mais qui n'est pas encore prise en charge par un moteur JavaScript particulier.
Nous pouvons ensuite l'implémenter manuellement et remplir le prototype intégré avec.
Par exemple:
if (!String.prototype.repeat) { // s'il n'existe pas de méthode de ce type // l'ajoute au prototype String.prototype.repeat = fonction(n) { // répète la chaîne n fois // en fait, le code devrait être un peu plus complexe que ça // (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
Dans le chapitre Décorateurs et expéditions, appeler/postuler nous avons parlé de la méthode d'emprunt.
C'est à ce moment-là que nous prenons une méthode d'un objet et la copions dans un autre.
Certaines méthodes de prototypes natifs sont souvent empruntées.
Par exemple, si nous créons un objet de type tableau, nous souhaiterons peut-être y copier certaines méthodes Array
.
Par exemple
soit obj = { 0 : "Bonjour", 1 : "monde !", longueur : 2, } ; obj.join = Array.prototype.join; alert( obj.join(',') ); // Bonjour le monde!
Cela fonctionne parce que l'algorithme interne de la méthode join
intégrée ne se soucie que des index corrects et de la propriété length
. Il ne vérifie pas si l'objet est bien un tableau. De nombreuses méthodes intégrées sont comme ça.
Une autre possibilité consiste à hériter en définissant 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 seul objet à la fois.
Les méthodes d'emprunt sont flexibles, elles permettent de mélanger les fonctionnalités de différents objets si nécessaire.
Tous les objets intégrés suivent le même modèle :
Les méthodes sont stockées dans le prototype ( Array.prototype
, Object.prototype
, Date.prototype
, etc.)
L'objet lui-même stocke uniquement les données (éléments du tableau, propriétés de l'objet, la date)
Les primitives stockent également des méthodes dans des prototypes d'objets wrapper : Number.prototype
, String.prototype
et Boolean.prototype
. Seuls les objets undefined
et null
n'ont pas d'objets wrapper
Les prototypes intégrés peuvent être modifiés ou complétés par de nouvelles méthodes. Mais il n'est pas recommandé de les changer. Le seul cas autorisé est probablement celui où nous ajoutons une nouvelle norme, mais elle n'est pas encore prise en charge par le moteur JavaScript.
importance : 5
Ajoutez au prototype de toutes les fonctions la méthode defer(ms)
, qui exécute la fonction après ms
millisecondes.
Après cela, le code suivant devrait fonctionner :
fonction f() { alert("Bonjour !"); } f.defer(1000); // affiche "Bonjour !" après 1 seconde
Function.prototype.defer = fonction(ms) { setTimeout(ce, ms); } ; fonction f() { alert("Bonjour !"); } f.defer(1000); // affiche "Bonjour !" après 1 seconde
importance : 4
Ajoutez au prototype de toutes les fonctions la méthode defer(ms)
, qui renvoie un wrapper, retardant l'appel de ms
millisecondes.
Voici un exemple de la façon dont cela devrait fonctionner :
fonction f(a, b) { alerte( a + b ); } f.defer(1000)(1, 2); // affiche 3 après 1 seconde
Veuillez noter que les arguments doivent être transmis à la fonction d'origine.
Function.prototype.defer = fonction(ms) { soit f = ceci ; fonction de retour(...arguments) { setTimeout(() => f.apply(this, args), ms); } } ; // vérifie-le fonction f(a, b) { alerte( a + b ); } f.defer(1000)(1, 2); // affiche 3 après 1 seconde
Remarque : nous this
utilisons dans f.apply
pour que notre décoration fonctionne pour les méthodes objet.
Ainsi, si la fonction wrapper est appelée en tant que méthode objet, this
est alors transmise à la méthode d'origine f
.
Function.prototype.defer = fonction(ms) { soit f = ceci ; fonction de retour(...arguments) { setTimeout(() => f.apply(this, args), ms); } } ; laissez l'utilisateur = { nom : "Jean", disBonjour() { alert(ce.nom); } } user.sayHi = user.sayHi.defer(1000); user.sayHi();