Présentation
Ce chapitre est la deuxième partie sur l'implémentation orientée objet d'ECMAScript. Dans la première partie, nous avons discuté de l'introduction et de la comparaison de CEMAScript. Si vous n'avez pas lu la première partie, avant de poursuivre ce chapitre, je vous le recommande fortement. que vous lisez la première partie. 1 article, car cet article est trop long (35 pages).
Texte original en anglais :http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Remarque : En raison de la longueur de cet article, des erreurs sont inévitables et sont constamment révisées.
Dans l'introduction, nous avons étendu à ECMAScript. Maintenant, quand nous connaissons son implémentation POO, définissons-la avec précision :
Type de données
Bien qu'ECMAScript soit un langage dynamiquement faiblement typé qui peut convertir dynamiquement des types, il possède toujours des types de données. En d’autres termes, un objet doit appartenir à un type réel.
Il existe 9 types de données définis dans la spécification standard, mais seuls 6 sont directement accessibles dans les programmes ECMAScript. Il s'agit de : Indéfini, Null, Booléen, Chaîne, Nombre et Objet.
Les trois autres types ne sont accessibles qu'au niveau de l'implémentation (les objets ECMAScript ne peuvent pas utiliser ces types) et sont utilisés dans les spécifications pour expliquer certains comportements opérationnels et enregistrer des valeurs intermédiaires. Ces 3 types sont : Référence, Liste et Complétion.
Par conséquent, Reference est utilisé pour expliquer les opérateurs tels que delete, typeof et this, et contient un objet de base et un nom de propriété ; List décrit le comportement de la liste de paramètres (dans les nouvelles expressions et les appels de fonction) ; pour expliquer le comportement des instructions break, continue, return et throw.
Types de valeurs primitives
En regardant les 6 types de données utilisés dans les programmes ECMAScript, les 5 premiers sont des types de valeurs primitifs, notamment Undefined, Null, Boolean, String, Number et Object.
Exemple de type de valeur primitive :
Ces valeurs sont implémentées directement sur la couche inférieure. Ce ne sont pas des objets, il n'y a donc pas de prototype ni de constructeur.
Note de l'oncle : Bien que ces valeurs nativessoient similaires en nom à celles que nous utilisons habituellement (Boolean, String, Number, Object), ce n'est pas la même chose. Par conséquent, les résultats de typeof(true) et typeof(Boolean) sont différents, car le résultat de typeof(Boolean) est une fonction, donc les fonctions Boolean, String et Number ont des prototypes (également mentionnés dans le chapitre sur les attributs de lecture et d'écriture ci-dessous) .
Si vous voulez savoir de quel type de données il s'agit, il est préférable d'utiliser typeof. Il existe un exemple auquel vous devez prêter attention. Si vous utilisez typeof pour déterminer le type de null, le résultat est un objet. Pourquoi? Parce que le type de null est défini comme Null.
La spécification n'imagine pas expliquer cela, mais Brendan Eich (l'inventeur de JavaScript) a remarqué que null est principalement utilisé dans les endroits où les objets apparaissent, par opposition à indéfini, comme définir un objet sur une référence nulle. Cependant, certaines personnes dans certains documents l'ont attribué à un bug et ont mis le bug dans la liste des bugs. Brendan Eich a également participé à la discussion. Le résultat a été que le résultat de typeof null a été défini sur object (malgré le 262-3 The). standard définit que le type de null est Null, et 262-5 a modifié la norme pour dire que le type de null est objet).
Type d'objet
Ensuite, le type Object (à ne pas confondre avec le constructeur Object, nous ne discutons maintenant que des types abstraits) est le seul type de données qui décrit les objets ECMAScript.
L'objet est une collection non ordonnée de paires clé-valeur.
Un objet est une collection non ordonnée de paires clé-valeur
La valeur clé d'un objet est appelée un attribut, et un attribut est un conteneur pour les valeurs primitives et d'autres objets. Si la valeur d'un attribut est une fonction, nous l'appelons une méthode.
Par exemple :
Dynamique
Comme nous l'avons souligné au chapitre 17, les objets dans ES sont complètement dynamiques. Cela signifie que nous pouvons ajouter, modifier ou supprimer les propriétés de l'objet à volonté pendant l'exécution du programme.
Par exemple :
Certaines propriétés ne peuvent pas être modifiées - (propriétés en lecture seule, propriétés supprimées ou propriétés non configurables). Nous l'expliquerons plus tard dans les propriétés des attributs.
De plus, la spécification ES5 stipule que les objets statiques ne peuvent pas être étendus avec de nouvelles propriétés et que leurs pages de propriétés ne peuvent pas être supprimées ou modifiées. Ce sont des objets dits gelés, qui peuvent être obtenus en appliquant la méthode Object.freeze(o).
Dans la spécification ES5, la méthode Object.preventExtensions(o) est également utilisée pour empêcher les extensions, ou la méthode Object.defineProperty(o) est utilisée pour définir des propriétés :
Objets intégrés, objets natifs et objets hôtes
Il est nécessaire de noter que la spécification fait également la distinction entre les objets intégrés, les objets élément et les objets hôtes.
Les objets intégrés et les objets élément sont définis et implémentés par la spécification ECMAScript, et les différences entre les deux sont insignifiantes. Tous les objets implémentés par ECMAScript sont des objets natifs (certains d'entre eux sont des objets intégrés, d'autres sont créés lors de l'exécution du programme, comme les objets définis par l'utilisateur). Les objets intégrés sont un sous-ensemble d'objets natifs intégrés à ECMAScript avant le démarrage du programme (par exemple, parseInt, Match, etc.). Tous les objets hôtes sont fournis par l'environnement hôte, généralement le navigateur, et peuvent inclure une fenêtre, une alerte, etc.
Notez que l'objet hôte peut être implémenté par ES lui-même, en respectant pleinement la sémantique de la spécification. De ce point de vue, ils peuvent être appelés objets « hôtes natifs » (le plus tôt possible théoriquement), mais la spécification ne définit pas la notion d'objets « hôtes natifs ».
Objets booléens, chaînes et numériques
De plus, la spécification définit également certaines classes d'emballage spéciales natives. Ces objets sont :
.1. Objet booléen
2. Objet chaîne
3. Objets numériques
Ces objets sont créés via les constructeurs intégrés correspondants et contiennent des valeurs natives comme propriétés internes. Ces objets peuvent convertir des valeurs primitives et vice versa.
De plus, il existe des objets créés par des constructeurs intégrés spéciaux : Function (constructeur d'objet de fonction), Array (constructeur de tableau) RegExp (constructeur d'expression régulière), Math (module mathématique), Date (constructeur de date) (conteneur) , etc. Ces objets sont également des valeurs de type objet Object. Leurs différences les unes par rapport aux autres sont gérées par des propriétés internes, dont nous discutons ci-dessous.
Litéral
Pour les valeurs de trois objets : objet, tableau et expression régulière, ils ont des identifiants abrégés appelés : initialiseur d'objet, initialiseur de tableau et expression régulière :
.
Notez que si les trois objets ci-dessus sont réaffectés à de nouveaux types, alors la sémantique d'implémentation ultérieure sera utilisée en fonction des types nouvellement attribués. Par exemple, dans l'implémentation actuelle de Rhino et l'ancienne version de SpiderMonkey 1.7, ce sera le cas. L'objet est créé avec succès à l'aide du constructeur du mot-clé new, mais dans certaines implémentations (actuellement Spider/TraceMonkey), la sémantique des littéraux ne change pas nécessairement après le changement de type.
Littéraux d'expression régulière et objets RegExp
Notez que dans les deux exemples suivants, la sémantique des expressions régulières est équivalente dans la troisième édition de la spécification. Le littéral regexp n'existe que dans une phrase et est créé lors de l'étape d'analyse, mais celui créé par le constructeur RegExp. is Il s'agit d'un nouvel objet, cela peut donc causer des problèmes. Par exemple, la valeur de lastIndex est erronée lors du test :
.
Tableau associatif
Diverses discussions textuelles statiques, les objets JavaScript (souvent créés à l'aide de l'initialiseur d'objet {}) sont appelés tables de hachage, tables de hachage ou autres noms simples : hash (un concept en Ruby ou Perl), management Array (un concept en PHP) , dictionnaire (un concept en Python), etc.
Il n'existe que de tels termes, principalement parce que leurs structures sont similaires, c'est-à-dire utiliser des paires « clé-valeur » pour stocker des objets, ce qui est tout à fait cohérent avec la structure de données définie par la théorie du « tableau associatif » ou du « hachage tableau". De plus, le type de données abstrait de table de hachage est généralement utilisé au niveau de l'implémentation.
Cependant, bien que la terminologie décrit ce concept, il s'agit en fait d'une erreur du point de vue d'ECMAScript : ECMAScript n'a qu'un seul objet, un seul type et ses sous-types, ce qui n'est pas différent du stockage par paire "clé-valeur", donc là. il n'y a pas de concept spécial à ce sujet. Parce que les propriétés internes de n'importe quel objet peuvent être stockées sous forme de paires clé-valeur :
De plus, puisque les objets peuvent être vides dans ECMAScript, le concept de « hachage » est également incorrect ici :
Veuillez noter que la norme ES5 nous permet de créer des objets sans prototypes (implémentés à l'aide de la méthode Object.create(null)). De ce point de vue, de tels objets peuvent être appelés tables de hachage :
Cependant, même si l'on considère que "hash" peut avoir un "prototype" (par exemple, une classe qui délègue des objets de hachage en Ruby ou Python), dans ECMAScript, cette terminologie est incorrecte car il y a un écart entre les deux représentations. Il n'y a pas de différence sémantique (c'est-à-dire en utilisant la notation par points ab et la notation a["b"]).
Le concept et la sémantique de « l'attribut de propriété » dans ECMAScript ne sont pas séparés de la « clé », de l'index du tableau et de la méthode. La lecture et l'écriture des propriétés de tous les objets ici doivent suivre les mêmes règles : vérifier la chaîne du prototype.
Dans l'exemple Ruby suivant, nous pouvons voir la différence sémantique :
Conversion d'objet
Pour convertir un objet en valeur primitive, vous pouvez utiliser la méthode valueOf. Comme nous l'avons dit, lorsque le constructeur de la fonction est appelé en fonction (pour certains types), mais si le mot-clé new n'est pas utilisé, le L'objet est converti en une valeur primitive, ce qui équivaut à l'appel implicite de la méthode valueOf :
La valeur par défaut de valueOf changera en fonction du type de l'objet (si elle n'est pas remplacée). Pour certains objets, elle renvoie ceci - par exemple : Object.prototype.valueOf(), et les valeurs calculées : Date.prototype. .valueOf() renvoie la date et l'heure :
La méthode toString définie sur Object.prototype a une signification particulière. Elle renvoie la valeur de l'attribut interne [[Class]] dont nous parlerons ci-dessous.
Par rapport à la conversion en valeurs primitives (ToPrimitive), la conversion de valeurs en types d'objets a également une spécification de conversion (ToObject).
Une méthode explicite consiste à utiliser le constructeur Object intégré comme fonction pour appeler ToObject (un peu similaire au nouveau mot-clé) :
Il n'y a pas de règles générales concernant l'appel des constructeurs intégrés, que ce soit pour utiliser l'opérateur new ou non, cela dépend du constructeur. Par exemple, Array ou Function produisent le même résultat lorsqu'ils sont utilisés comme constructeur utilisant l'opérateur new ou comme fonction simple qui n'utilise pas l'opérateur new :
Caractéristiques des attributs
Toutes les propriétés peuvent avoir de nombreux attributs.
1.{ReadOnly} - Ignorez l'opération d'écriture consistant à attribuer une valeur à la propriété, mais la propriété en lecture seule peut être modifiée par le comportement de l'environnement hôte - c'est-à-dire qu'il ne s'agit pas d'une "valeur constante" ;
2.{DontEnum}——Les attributs ne peuvent pas être énumérés par la boucle for..in
3.{DontDelete}——Le comportement de l'opérateur de suppression est ignoré (c'est-à-dire qu'il ne peut pas être supprimé) ;
4. {Interne} - Attribut interne, pas de nom (utilisé uniquement au niveau de l'implémentation), ces attributs ne sont pas accessibles dans ECMAScript.
Notez que dans ES5 {ReadOnly}, {DontEnum} et {DontDelete} sont renommés en [[Writable]], [[Enumerable]] et [[Configurable]], qui peuvent être transmis manuellement via Object.defineProperty ou similaire méthodes pour gérer ces propriétés.
Propriétés et méthodes internes
Les objets peuvent également avoir des propriétés internes (faisant partie du niveau d'implémentation) qui ne sont pas directement accessibles aux programmes ECMAScript (mais comme nous le verrons ci-dessous, certaines implémentations permettent d'accéder à certaines de ces propriétés). Ces propriétés sont accessibles via des crochets imbriqués [[ ]]. Jetons un coup d'œil à certaines d'entre elles. La description de ces propriétés se trouve dans le cahier des charges.
Chaque objet doit implémenter les propriétés et méthodes internes suivantes :
1.[[Prototype]] - le prototype de l'objet (sera présenté en détail ci-dessous)
2.[[Class]] - une représentation d'un objet chaîne (par exemple, Object Array, Function Object, Function, etc.) utilisée pour distinguer les objets
;
3.[[Get]]——Méthode pour obtenir la valeur de l'attribut
4.[[Put]]——Méthode pour définir la valeur de l'attribut
5.[[CanPut]]——Vérifiez si l'attribut est accessible en écriture
6.[[HasProperty]]——Vérifiez si l'objet possède déjà cette propriété
7.[[Supprimer]]——Supprimer l'attribut de l'objet
8.[[DefaultValue]] renvoie la valeur d'origine de l'objet (en appelant la méthode valueOf, certains objets peuvent lever une exception TypeError).
La valeur de la propriété interne [[Class]] peut être obtenue indirectement via la méthode Object.prototype.toString(), qui doit renvoyer la chaîne suivante : "[object " [[Class]] "]" . Par exemple :
Constructeur
Ainsi, comme nous l'avons mentionné ci-dessus, les objets dans ECMAScript sont créés via ce qu'on appelle des constructeurs.
Le constructeur est une fonction qui crée et initialise l'objet nouvellement créé.
Un constructeur est une fonction qui crée et initialise un objet nouvellement créé.
La création d'objets (allocation de mémoire) est prise en charge par la méthode interne du constructeur [[Construct]]. Le comportement de cette méthode interne est bien défini et tous les constructeurs utilisent cette méthode pour allouer de la mémoire aux nouveaux objets.
L'initialisation est gérée en appelant cette fonction de haut en bas du nouvel objet, qui est responsable de la méthode interne [[Call]] du constructeur.
Notez que le code utilisateur n'est accessible que pendant la phase d'initialisation, bien que pendant la phase d'initialisation, nous puissions renvoyer un objet différent (en ignorant l'objet créé dans la première phase) :
En nous référant au chapitre 15 Fonction - Algorithme de création de fonctions, nous pouvons voir que la fonction est un objet natif, comprenant les attributs [[Construct]] ] et [[Call]] ] ainsi que l'attribut prototype affiché - le futur Le prototype de l'objet (Remarque : NativeObject est une convention pour les objets natifs et est utilisé dans le pseudocode ci-dessous).
[[Call]] ] est le principal moyen de distinguer les objets autres que l'attribut [[Class]] (ici équivalent à "Fonction"), donc l'attribut interne [[Call]] de l'objet est appelé comme un fonction. L'utilisation de l'opérateur typeof sur un tel objet renvoie "fonction". Cependant, cela est principalement lié aux objets natifs. Dans certains cas, l'implémentation de l'utilisation de typeof pour obtenir la valeur est différente : l'effet de window.alert (...) dans IE :
.
La méthode interne [[Construct]] est activée en utilisant le constructeur avec l'opérateur new Comme nous l'avons dit, cette méthode est responsable de l'allocation de mémoire et de la création d'objets. S'il n'y a pas de paramètres, les parenthèses pour appeler le constructeur peuvent également être omises :
Étudions l'algorithme de création d'objets.
Algorithme de création d'objets
Le comportement de la méthode interne [[Construct]] peut être décrit comme suit :
Veuillez noter deux caractéristiques principales :
1. Premièrement, le prototype de l'objet nouvellement créé est obtenu à partir de l'attribut prototype de la fonction à l'heure actuelle (cela signifie que les prototypes de deux objets créés par le même constructeur peuvent être différents car l'attribut prototype de la fonction peut également être différente) .
2. Deuxièmement, comme nous l'avons mentionné ci-dessus, si [[Call]] renvoie un objet lorsque l'objet est initialisé, c'est exactement le résultat utilisé pour l'ensemble de l'opérateur new :
Prototype
Chaque objet possède un prototype (sauf certains objets système). La communication du prototype s'effectue via la propriété prototype interne, implicite et non directement accessible [[Prototype]] Le prototype peut être un objet ou une valeur nulle.
Constructeur de propriété
Il y a deux points de connaissance importants dans l'exemple ci-dessus. Le premier concerne l'attribut prototype de l'attribut constructeur de la fonction. Dans l'algorithme de création de fonction, nous savons que l'attribut constructeur est défini sur l'attribut prototype de la fonction. fonction pendant la phase de création de la fonction, la valeur de l'attribut constructeur est une référence importante à la fonction elle-même :
Habituellement, dans ce cas, il y a un malentendu : la propriété constructor constructor est fausse en tant que propriété de l'objet nouvellement créé lui-même, mais, comme nous pouvons le voir, cette propriété appartient au prototype et est accessible par héritage.
En héritant de l'instance de l'attribut constructeur, vous pouvez obtenir indirectement une référence à l'objet prototype :
Mais veuillez noter que les attributs constructeur et prototype de la fonction peuvent être redéfinis après la création de l'objet. Dans ce cas, l'objet perd le mécanisme décrit ci-dessus. Si vous modifiez le prototype de l'élément via l'attribut prototype de la fonction (en ajoutant un nouvel objet ou en modifiant un objet existant), vous verrez les attributs nouvellement ajoutés sur l'instance.
Cependant, si nous modifions complètement la propriété prototype de la fonction (en allouant un nouvel objet), la référence au constructeur d'origine est perdue, car l'objet que nous créons n'inclut pas la propriété constructeur :
Notez que bien que l'attribut constructeur ait été restauré manuellement, par rapport au prototype original perdu, la fonctionnalité {DontEnum} n'est plus disponible, ce qui signifie que l'instruction de boucle for..in dans A.prototype n'est pas prise en charge, mais dans la 5e édition de la spécification, offre la possibilité de contrôler l'état énumérable énumérable via l'attribut [[Enumerable]].
Prototype explicite et attributs [[Prototype]] implicites
De manière générale, il est incorrect de référencer explicitement le prototype d'un objet via l'attribut prototype de la fonction. Il fait référence au même objet, l'attribut [[Prototype]] de l'objet :
a.[[Prototype]] ----> Prototype <---- A.prototype
De plus, la valeur [[Prototype]] de l'instance est bien obtenue à partir de l'attribut prototype du constructeur.
Cependant, la soumission de l'attribut prototype n'affectera pas le prototype de l'objet déjà créé (il ne sera affecté que lorsque l'attribut prototype du constructeur changera, c'est-à-dire que seuls les objets nouvellement créés auront de nouveaux prototypes, et). les objets déjà créés auront toujours de nouveaux prototypes. Référence à l'ancien prototype original (ce prototype ne peut plus être modifié).
Par exemple :
La règle principale ici est la suivante : le prototype d'un objet est créé lors de la création de l'objet, et ne peut pas être modifié en un nouvel objet par la suite. S'il fait toujours référence au même objet, il peut être référencé via le prototype explicite. du constructeur. Une fois l'objet créé, seules les propriétés du prototype peuvent être ajoutées ou modifiées.
Attribut __proto__ non standard
Cependant, certaines implémentations, telles que SpiderMonkey, fournissent l'attribut explicite non standard __proto__ pour référencer le prototype de l'objet :
Objet indépendant du constructeur
Le prototype de l'instance étant indépendant du constructeur et de l'attribut prototype du constructeur, le constructeur peut être supprimé après avoir terminé son travail principal (création de l'objet). Les objets prototypes continuent d'exister en référençant l'attribut [[Prototype]] :
Caractéristiques de l'opérateur instanceof
Nous affichons le prototype de référence via l'attribut prototype du constructeur, qui est lié à l'opérateur instanceof. Cet opérateur travaille avec la chaîne de prototypes, pas avec le constructeur. Dans cette optique, il y a souvent des malentendus lors de la détection d'objets :
Regardons cet exemple :
D'un autre côté, un objet peut être créé par un constructeur, mais si l'attribut [[Prototype]] de l'objet et la valeur de l'attribut prototype du constructeur sont définis sur la même valeur, instanceof renverra true une fois coché :
Les prototypes peuvent stocker des méthodes et partager des propriétés
Les prototypes sont utilisés dans la plupart des programmes pour stocker les méthodes d'objet, les états par défaut et les propriétés d'objet partagées.
En fait, les objets peuvent avoir leur propre état, mais les méthodes sont généralement les mêmes. Par conséquent, les méthodes sont généralement définies dans des prototypes pour l’optimisation de la mémoire. Cela signifie que toutes les instances créées par ce constructeur peuvent partager cette méthode.
Lecture et écriture des attributs
Comme nous l'avons mentionné, la lecture et l'écriture des valeurs de propriété se font via les méthodes internes [[Get]] et [[Put]]. Ces méthodes internes sont activées via des accesseurs de propriétés : notation par points ou notation par index :
Méthode [[Get]]
[[Get]] interrogera également les propriétés de la chaîne de prototypes, de sorte que les propriétés du prototype soient également accessibles via l'objet.
O.[[Obtenir]](P):
Remarque : L'opérateur in peut également être chargé de rechercher des propriétés (il recherchera également la chaîne de prototypes) :
Méthode [[Put]]
La méthode[[Put]] peut créer et mettre à jour les propriétés de l'objet lui-même, et masquer les propriétés du même nom dans le prototype.
O.[[Put]](P, V):
// Par exemple, l'attribut length est en lecture seule, essayons de masquer la longueur
fonction SuperString() {
/* rien */
>
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, la longueur de "abc"
// Tentative de masque
foo.length = 5;
console.log(foo.length); // Toujours 3
Accesseur de propriété
Les méthodes internes [[Get]] et [[Put]] sont activées via la notation par points ou l'indexation dans ECMAScript. Si l'identifiant de l'attribut est un nom légal, il est accessible via ".", et l'indexation Party s'exécute de manière dynamique. noms définis.
Il y a une fonctionnalité très importante ici : les accesseurs de propriété utilisent toujours la spécification ToObject pour traiter la valeur à gauche de "." Cette conversion implicite est liée au dicton « tout en JavaScript est un objet » (cependant, comme nous le savons déjà, toutes les valeurs en JavaScript ne sont pas des objets).
Si l'accesseur d'attribut est utilisé pour accéder à la valeur d'origine, la valeur d'origine sera encapsulée par l'objet (y compris la valeur d'origine) avant l'accès, puis l'attribut sera accessible via l'objet encapsulé après l'accès à l'attribut. , l'objet enveloppé sera supprimé.
Par exemple :
La réponse est simple :
Tout d'abord, comme nous l'avons dit, après avoir utilisé l'accesseur de propriété, ce n'est plus la valeur d'origine, mais un objet intermédiaire encapsulé (l'exemple entier utilise new Number(a)), et la méthode toString est passée à ce niveau. time Trouvé dans la chaîne des prototypes :
Nous voyons qu'à l'étape 3, l'objet enveloppé est supprimé et la page de propriétés nouvellement créée est supprimée - supprimant l'objet d'emballage lui-même.
Lors de l'utilisation de [[Get]] pour obtenir la valeur de test, l'objet d'emballage est à nouveau créé, mais cette fois l'objet enveloppé n'a plus l'attribut de test, donc undefined est renvoyé :
Hériter
Nous savons qu'ECMAScript utilise l'héritage délégué basé sur des prototypes. Les chaînes et prototypes ont déjà été évoqués dans la chaîne de prototypes. En fait, toute la mise en œuvre de la délégation ainsi que la recherche et l'analyse de la chaîne de prototypes sont condensées dans la méthode [[Get]].
Si vous comprenez parfaitement la méthode [[Get]], alors la question de l'héritage en JavaScript sera explicite.
Quand je parle souvent d'héritage en JavaScript sur les forums, j'utilise toujours une ligne de code pour le montrer. En fait, nous n'avons pas besoin de créer d'objets ou de fonctions car le langage est déjà basé sur l'héritage. est la suivante :
1. Tout d'abord, créez un objet d'emballage à partir de la valeur d'origine 1 jusqu'au nouveau Number(1)
2. Ensuite, la méthode toString est héritée de cet objet de packaging
Pourquoi est-il hérité ? Étant donné que les objets dans ECMAScript peuvent avoir leurs propres propriétés, l'objet wrapper dans ce cas n'a pas de méthode toString. Il hérite donc du principe qui est Number.prototype.
Notez qu'il y a une subtilité, les deux points dans l'exemple ci-dessus ne sont pas une erreur. Le premier point représente la partie décimale, et le deuxième point est un accesseur d'attribut :
Chaîne prototype
Montrons comment créer une chaîne de prototypes pour un objet défini par l'utilisateur, c'est très simple :
Cette méthode présente deux caractéristiques :
Tout d'abord, B.prototype contiendra l'attribut x. À première vue, cela peut sembler faux, vous pourriez penser que la propriété x est définie dans A et que le constructeur B l'attend également. Bien que l'héritage prototypique ne pose aucun problème dans des circonstances normales, le constructeur B peut parfois ne pas avoir besoin de l'attribut x. Par rapport à l'héritage basé sur les classes, tous les attributs sont copiés dans les sous-classes descendantes.
Cependant, s'il est nécessaire (pour simuler l'héritage basé sur les classes) d'attribuer l'attribut x à l'objet créé par le constructeur B, il existe plusieurs moyens, dont nous montrerons l'un plus tard.
Deuxièmement, ce n'est pas une fonctionnalité mais un inconvénient - lorsque le prototype de la sous-classe est créé, le code du constructeur est également exécuté, et on peut voir que le message "A.[[Call]] activé" s'affiche deux fois - Lorsque vous utilisez le constructeur A pour créer un objet et l'attribuer à la propriété B.prototype, l'autre scène est celle où l'objet se crée !
L'exemple suivant est plus critique, l'exception levée par le constructeur de la classe parent : peut-être doit-elle être vérifiée lors de la création de l'objet réel, mais évidemment, c'est le même cas, c'est-à-dire lors de l'utilisation de ces objets parents comme prototypes Quelque chose va mal se passer.
De plus, avoir trop de code dans le constructeur de la classe parent est aussi un inconvénient.
Pour résoudre ces "fonctions" et problèmes, les programmeurs utilisent le modèle standard de chaînes de prototypes (illustré ci-dessous). L'objectif principal est d'envelopper la création de constructeurs au milieu. Les chaînes de ces constructeurs d'emballage contiennent les prototypes requis.