Dans le premier chapitre de cette section, nous avons indiqué quâil existe des méthodes modernes pour configurer un prototype.
La définition ou la lecture du prototype avec obj.__proto__ est considérée comme obsolète et dépréciée (déplacée dans la soi-disant âannexe Bâ de la norme JavaScript, destinée uniquement aux navigateurs).
Les méthodes modernes pour obtenir/définir un prototype sont :
- Object.getPrototypeOf(obj) â retourn le
[[Prototype]]deobj. - Object.setPrototypeOf(obj, proto) â configure le
[[Prototype]]deobjÃproto.
La seule utilisation de __proto__, qui nâest pas mal vue, est en tant que propriété lors de la création dâun nouvel objet : { __proto__: ... }.
Bien quâil existe également une méthode spéciale pour cela :
- Object.create(proto, [descriptors]) â crée un objet vide avec
protodonné comme[[Prototype]]et des descripteurs de propriété facultatifs.
Par exemple :
let animal = {
eats: true
};
// créer un nouvel objet avec animal comme prototype
let rabbit = Object.create(animal); // identique à {__proto__: animal}
alert(rabbit.eats); // true
alert(Object.getPrototypeOf(rabbit) === animal); // true
Object.setPrototypeOf(rabbit, {}); // change le prototype de rabbit en {}
La méthode Object.create est un peu plus puissante, car elle a un deuxième argument facultatif : les descripteurs de propriété.
Nous pouvons fournir des propriétés supplémentaires au nouvel objet, comme ceci :
let animal = {
eats: true
};
let rabbit = Object.create(animal, {
jumps: {
value: true
}
});
alert(rabbit.jumps); // true
Les descripteurs sont dans le même format que décrit dans le chapitre Attributs et descripteurs de propriétés.
Nous pouvons utiliser Object.create pour effectuer un clonage dâobjet plus puissant que la copie des propriétés dans la boucle for..in :
let clone = Object.create(
Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)
);
Cet appel crée une copie véritablement exacte de obj, y compris de toutes les propriétés : énumérable et non-énumérable, des propriétés de données et des accesseurs/mutateurs â tout, et avec le bon [[Prototype]].
Bref historique
Il y a tellement de façons de gérer [[Prototype]]. Comment est-ce arrivé ? Pourquoi ?
Câest pour des raisons historiques.
Lâhéritage prototypal était dans le langage depuis son aube, mais les façons de le gérer ont évolué au fil du temps.
- La propriété
prototypedâune fonction constructeur fonctionne depuis des temps très anciens. Câest la manière la plus ancienne de créer des objets avec un prototype donné. - Plus tard, en 2012,
Object.createest apparu dans la norme. Il a donné la possibilité de créer des objets avec un prototype donné, mais nâa pas fourni la possibilité de lâobtenir/le définir. Certains navigateurs ont implémenté lâaccesseur non standard__proto__qui permettait à lâutilisateur dâobtenir/définir un prototype à tout moment, pour donner plus de flexibilité aux développeurs. - Plus tard, en 2015,
Object.setPrototypeOfetObject.getPrototypeOfont été ajoutés à la norme, pour exécuter la même fonctionnalité que__proto__. Comme__proto__était de facto implémenté partout, il était en quelque sorte obsolète et a fait son chemin vers lâannexe B de la norme, câest-à -dire facultatif pour les environnements sans navigateur. - Plus tard, en 2022, il a été officiellement autorisé dâutiliser
__proto__dans les objets littéraux{...}(sortie de lâannexe B), mais pas en tant que getter/setterobj.__proto__(toujours dans lâannexe B).
Pourquoi __proto__ a été remplacé par les fonctions getPrototypeOf/setPrototypeOf ?
Pourquoi __proto__ a-t-il été partiellement réhabilité et son utilisation autorisée dans {...}, mais pas en tant que getter/setter ?
Câest une question intéressante, qui nous oblige à comprendre pourquoi __proto__ est mauvais.
Et bientôt nous aurons la réponse.
[[Prototype]] sur des objets existants si la vitesse est importanteTechniquement, nous pouvons accéder/muter [[Prototype]] à tout moment. Mais en général, nous ne le définissons quâune fois au moment de la création de lâobjet, puis nous ne le modifions pas : rabbit hérite de animal, et cela ne changera pas.
Et les moteurs JavaScript sont hautement optimisés pour cela. Changer un prototype âà la voléeâ avec Object.setPrototypeOf ou obj.__ proto __= est une opération très lente, elle rompt les optimisations internes pour des opérations dâaccès aux propriétés dâobjet. Alors évitez-la à moins que vous ne sachiez ce que vous faites, ou que la vitesse de JavaScript nâa pas dâimportance pour vous.
Objets "très simples"
Comme nous le savons, les objets peuvent être utilisés en tant que tableaux associatifs pour stocker des paires clé/valeur.
â¦Mais si nous essayons de stocker des clés fournies par lâutilisateur (par exemple, un dictionnaire saisi par lâutilisateur), nous verrons un petit problème intéressant : toutes les clés fonctionnent très bien, sauf "__proto __".
Découvrez lâexemple :
let obj = {};
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // [object Object], pas "some value" !
Ici, si lâutilisateur tape __proto__, lâassignation à la ligne 4 est ignorée !
Cela pourrait sûrement être surprenant pour un non-développeur, mais assez compréhensible pour nous. La propriété __proto__ est spéciale : elle doit être soit un objet, soit null. Une chaîne de caractères ne peut pas devenir un prototype. Câest pourquoi une affectation dâune chaîne à __proto__ est ignorée.
Mais nous nâavions pas lâintention de mettre en Åuvre un tel comportement, non ? Nous voulons stocker des paires clé/valeur, et la clé nommée "__proto__" nâa pas été correctement enregistrée. Donc câest un bug !
Ici les conséquences ne sont pas terribles. Mais dans dâautres cas, nous pouvons stocker des objets au lieu de chaînes dans obj, puis le prototype sera effectivement modifié. En conséquence, lâexécution ira mal de manière totalement inattendue.
Ce qui est pire â généralement les développeurs ne pensent pas du tout à cette possibilité. Cela rend ces bugs difficiles à remarquer et même à les transformer en vulnérabilités, en particulier lorsque JavaScript est utilisé côté serveur.
Des choses inattendues peuvent également se produire lors de lâaffectation à obj.toString, car il sâagit dâune méthode dâobjet intégrée.
Comment pouvons-nous éviter ce problème ?
Tout dâabord, nous pouvons simplement passer à lâutilisation de Map pour le stockage au lieu dâobjets simples, puis tout va bien.
let map = new Map();
let key = prompt("What's the key?", "__proto__");
map.set(key, "some value");
alert(map.get(key)); // "some value" (comme prévu)
â¦Mais la syntaxe Object est souvent plus attrayante, car elle est plus concise.
Heureusement, nous pouvons utiliser des objets, car les créateurs du langage ont réfléchi à ce problème il y a longtemps.
Comme nous le savons, __proto__ nâest pas une propriété dâun objet, mais un accesseur sur la propriété Object.prototype :
Ainsi, si obj.__proto__ est lu ou muté, lâaccésseur/mutateur correspondant est appelé à partir de son prototype et il accède/mute [[Prototype]].
Comme il a été dit au début de cette section de tutoriel : __proto__ est un moyen dâaccéder [[Prototype]], il nâest pas [[Prototype]] lui-même.
Maintenant, si nous avons lâintention dâutiliser un objet comme tableau associatif et de ne pas avoir de tels problèmes, nous pouvons le faire avec une petite astuce :
let obj = Object.create(null);
// ou : obj = { __proto__: null }
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Object.create(null) crée un objet vide sans prototype ([[Prototype]] est null) :
Donc, il nây a pas dâaccésseur/mutateur hérité pour __proto__. Maintenant, il est traité comme une propriété de données normale, ainsi lâexemple ci-dessus fonctionne correctement.
Nous pouvons appeler de tels objets des objets âtrès simplesâ ou âdictionnaire purâ, car ils sont encore plus simples que les objets simples ordinaires {...}.
Lâinconvénient est que de tels objets ne possèdent aucune méthode dâobjet intégrée, par exemple toString :
let obj = Object.create(null);
alert(obj); // Error (pas de toString)
â¦Mais câest généralement acceptable pour les tableaux associatifs.
Notez que la plupart des méthodes liées aux objets sont Object.quelquechose(...), comme Object.keys(obj) â elles ne sont pas dans le prototype, elles continueront donc à travailler sur de tels objets :
let chineseDictionary = Object.create(null);
chineseDictionary.hello = "ä½ å¥½";
chineseDictionary.bye = "åè§";
alert(Object.keys(chineseDictionary)); // hello,bye
Résumé
-
Pour créer un objet avec le prototype donné, utilisez :
- la syntaxe littérale :
{ __proto__: ... }, permet de spécifier plusieurs propriétés - ou Object.create(proto, [descriptors]), permet de spécifier des descripteurs de propriété.
Le
Object.createfournit un moyen simple de copier superficiellement un objet avec tous les descripteurs :let clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj)); - la syntaxe littérale :
-
Les méthodes modernes pour obtenir/définir le prototype sont :
- Object.getPrototypeOf(obj) â renvoie le
[[Prototype]]deobj(identique au getter__proto__). - Object.setPrototypeOf(obj, proto) â définit le
[[Prototype]]deobjÃproto(identique au setter__proto__).
- Object.getPrototypeOf(obj) â renvoie le
-
Obtenir/définir le prototype en utilisant le getter/setter intégré.
__proto__nâest pas recommandé, il est maintenant dans lâannexe B de la spécification. -
Nous avons également couvert les objets sans prototype, créés avec
Object.create(null)ou{__proto__: null}.Ces objets sont utilisés comme dictionnaires, pour stocker toutes les clés (éventuellement générées par lâutilisateur).
Normalement, les objets héritent des méthodes intégrées et du getter/setter
__proto__deObject.prototype, rendant les clés correspondantes âoccupéesâ et provoquant potentiellement des effets secondaires. Avec le prototype ânullâ, les objets sont vraiment vides.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâ¦)