Maison >interface Web >js tutoriel >Compréhension approfondie de la série JavaScript (18) : implémentation ECMAScript de la programmation orientée objet_Connaissances de base

Compréhension approfondie de la série JavaScript (18) : implémentation ECMAScript de la programmation orientée objet_Connaissances de base

WBOY
WBOYoriginal
2016-05-16 16:11:121387parcourir

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 :

Copier le code Le code est le suivant :

ECMAScript est un langage de programmation orienté objet prenant en charge la délégation d'héritage basée sur des prototypes.

ECMAScript est un langage orienté objet qui prend en charge l'héritage délégué basé sur des prototypes.
Nous l'analyserons à partir des types de données les plus élémentaires. La première chose à comprendre est que ECMAScript utilise des valeurs et des objets primitifs pour distinguer les entités. Par conséquent, ce que disent certains articles "En JavaScript, tout est un objet" est faux (pas tout à fait). à droite), les valeurs primitives font partie des types de données dont nous allons discuter ici.

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 :

Copier le code Le code est le suivant :

var a = indéfini ;
var b = nul;
var c = vrai;
var d = 'test';
var e = 10;

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 natives​​soient 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.

Copier le code Le code est le suivant :

alert(typeof null); // "objet"

La raison pour laquelle "objet" est affiché est que la spécification stipule que la valeur de chaîne typeof renvoie "objet" pour une valeur 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 :

Copier le code Le code est le suivant :

var x = { // L'objet "x" a 3 attributs : a, b, c
a : 10, // valeur d'origine
b : {z : 100}, // L'objet "b" a un attribut z
c: function () { // fonction (méthode)
alert('méthode x.c');
>
};

alert(x.a); // 10
alert(x.b); // [objet Objet]
alerte(x.b.z); // 100
x.c(); // 'méthode x.c'

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 :

Copier le code Le code est le suivant :

var foo = {x : 10};

//Ajouter un nouvel attribut
foo.y = 20;
console.log(foo); // {x : 10, y : 20}

// Modifier la valeur de l'attribut en fonction
foo.x = fonction () {
console.log('foo.x');
};

foo.x(); // 'foo.x'

// Supprimer l'attribut
supprimer foo.x;
console.log(foo); // {y : 20}

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).

Copier le code Le code est le suivant :

var foo = {x : 10};

// Geler l'objet
Objet.freeze(foo);
console.log(Object.isFrozen(foo)); // vrai

// Ne peut pas être modifié
foo.x = 100;

// Ne peut pas être développé
foo.y = 200;

// Impossible de supprimer
supprimer foo.x;

console.log(foo); // {x : 10}

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 :

Copier le code Le code est le suivant :

var foo = {x : 10};

Object.defineProperty(foo, "y", {
valeur : 20,
inscriptible : faux, // lecture seule
configurable : false // Non configurable
});

// Ne peut pas être modifié
foo.y = 200;

// Impossible de supprimer
supprimer foo.y; // faux

// Extension Prévention et Contrôle
Object.preventExtensions(foo);
console.log(Object.isExtensible(foo)); // false

//Impossible d'ajouter de nouveaux attributs
foo.z = 30;

console.log(foo); {x : 10, y : 20}

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.

Copier le code Le code est le suivant :

var c = nouveau Boolean(true);
var d = new String('test');
var e = nouveau Nombre (10);

//Convertir en valeur d'origine
// Utiliser la fonction sans nouveau mot-clé
с = Booléen(c);
d = Chaîne(d);
e = Nombre(e);

// Reconvertir en objet
с = Objet(c);
d = Objet(d);
e = Objet(e);

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 :
.

Copier le code Le code est le suivant :

// Équivalent à new Array(1, 2, 3);
// Ou array = new Array();
// tableau[0] = 1;
// tableau[1] = 2;
// tableau[2] = 3;
var tableau = [1, 2, 3];

// Équivalent à
// var objet = new Object();
// objet.a = 1;
// objet.b = 2;
// objet.c = 3;
var objet = {a : 1, b : 2, c : 3};

// Équivalent à new RegExp("^\d $", "g")
var re = /^d $/g;

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.

Copier le code Le code est le suivant :

var getClass = Object.prototype.toString;

Objet = Nombre ;

var foo = nouvel objet ;
alert([foo, getClass.call(foo)]); // 0, "[numéro d'objet]"

var barre = {};

// Rhino, SpiderMonkey 1.7 - 0, "[numéro d'objet]"
// Autres : toujours "[objet Objet]", "[objet Objet]"
alert([bar, getClass.call(bar)]);

//Le tableau a le même effet
Tableau = Nombre ;

foo = nouveau tableau ;
alert([foo, getClass.call(foo)]); // 0, "[numéro d'objet]"

barre = [];

// Rhino, SpiderMonkey 1.7 - 0, "[numéro d'objet]"
// Autres : toujours "", "[objet Objet]"
alert([bar, getClass.call(bar)]);

// Mais pour RegExp, la sémantique du littéral n'est pas modifiée. sémantique du littéral
// n'est pas modifié dans toutes les implémentations testées

RegExp = Nombre ;

foo = nouvelle RegExp;
alert([foo, getClass.call(foo)]); // 0, "[numéro d'objet]"

barre = /(?!)/g;
alert([bar, getClass.call(bar)]); // /(?!)/g, "[object RegExp]"

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 :
.

Copier le code Le code est le suivant :

pour (var k = 0; k < 4; k ) {
var re = /ecma/g;
alerte(re.lastIndex); // 0, 4, 0, 4
alert(re.test("ecmascript")); // vrai, faux, vrai, faux
>

// Comparez

pour (var k = 0; k < 4; k ) {
var re = new RegExp("ecma", "g");
alerte(re.lastIndex); // 0, 0, 0, 0
alert(re.test("ecmascript")); // vrai, vrai, vrai, vrai
>

Remarque : Cependant, ces problèmes ont été corrigés dans la 5ème édition de la spécification ES. Qu'elle soit basée sur des littéraux ou des constructeurs, les règles régulières créent de nouveaux objets.

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 :

Copier le code Le code est le suivant :

var une = {x : 10} ;
a['y'] = 20;
a.z = 30;

var b = nouveau Nombre(1);
b.x = 10;
par = 20;
b['z'] = 30;

var c = nouvelle fonction('');
c.x = 10;
c.y = 20;
c['z'] = 30;

// Attendez, un sous-type de n'importe quel objet "sous-type"

De plus, puisque les objets peuvent être vides dans ECMAScript, le concept de « hachage » est également incorrect ici :

Copier le code Le code est le suivant :

Objet.prototype.x = 10;

var a = {}; // Créer un "hash" vide

alert(a["x"]); // 10, mais pas vide
alert(a.toString); // fonction

a["y"] = 20; // Ajouter une nouvelle paire clé-valeur au "hash"
alerte(a["y"]); // 20

Object.prototype.y = 20; // Ajouter des attributs de prototype

supprimer a["y"]; // Supprimer
alert(a["y"]); // Mais la clé et la valeur ici ont toujours des valeurs - 20

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 :

Copier le code Le code est le suivant :

var aHashTable = Object.create(null);
console.log(aHashTable.toString); // Non défini

De plus, certaines propriétés ont des méthodes getter/setter spécifiques, ce qui peut également prêter à confusion sur ce concept :
Copier le code Le code est le suivant :

var a = new String("foo");
a['longueur'] = 10;
alert(a['longueur']); // 3

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 :

Copier le code Le code est le suivant :

une = {}
a.class # Hash

a.longueur #0

# nouvelle paire "clé-valeur"
a['longueur'] = 10;

# Sémantiquement, les points sont utilisés pour accéder aux propriétés ou aux méthodes, pas à la clé

a.longueur #1

#L'indexeur accède à la clé dans le hachage

a['longueur'] #10

# Cela revient à déclarer dynamiquement une classe Hash sur un objet existant
# Puis déclarez de nouvelles propriétés ou méthodes

hachage de classe
déf z
100
fin
fin

# De nouveaux attributs sont accessibles

a.z #100

#Mais pas "clé"

a['z'] # néant

La norme ECMA-262-3 ne définit pas la notion de « hachages » (et assimilés). Cependant, s’il existe une telle théorie structurelle, il est possible de donner son nom à l’objet.

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 :

Copier le code Le code est le suivant :

var a = nouveau Nombre(1);
var primitiveA = Number(a); // Appel implicite "valueOf"
var alsoPrimitiveA = a.valueOf(); // Appel explicite

alerte([
typeof a, // "objet"
type de primitiveA, // "numéro"
typeofalsoPrimitiveA // "numéro"
]);

Cette approche permet aux objets de participer à diverses opérations, telles que :
Copier le code Le code est le suivant :

var a = nouveau Nombre(1);
var b = nouveau Nombre(2);

alerte(a b); // 3

// Même

var c = {
x : 10,
y : 20,
valueOf : fonction () {
Renvoie this.x this.y;
>
};

vard = {
x : 30,
y : 40,
//Identique à la fonction valueOf de c
valueOf : c.valueOf
};

alerte(c d); // 100

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 :

Copier le code Le code est le suivant :

var une = {};
alert(a.valueOf() === a); // true, "valueOf" renvoie ceci

var d = nouvelle Date();
alert(d.valueOf()); // heure
alert(d.valueOf() === d.getTime()); // true

De plus, les objets ont une représentation plus primitive : une représentation sous forme de chaîne. Cette méthode toString est fiable et est utilisée automatiquement pour certaines opérations :
Copier le code Le code est le suivant :

var une = {
valueOf : fonction () {
Renvoyez 100 ;
},
toString : fonction () {
Renvoie '__test';
>
};

// Dans cette opération, la méthode toString est automatiquement appelée
alerte(a); // "__test"

// Mais ici, la méthode valueOf() est appelée
alerte(un 10); // 110

// Mais, une fois que valueOf est supprimé
// toString peut être rappelé automatiquement
supprimer a.valueOf;
alerte(un 10); // "_test10"

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é) :

Copier le code Le code est le suivant :

var n = Objet(1); // [numéro d'objet]
var s = Objet('test'); // [objet Chaîne]

//Quelque chose de similaire, vous pouvez également utiliser le nouvel opérateur
var b = new Object(true); // [objet Boolean]

// Si le paramètre new Object est utilisé, un objet simple est créé
var o = new Object(); // [objet Objet]

// Si le paramètre est un objet existant
// Le résultat de la création est de simplement renvoyer l'objet
var a = [];
alert(a === new Object(a)); // true
alert(a === Objet(a)); // vrai

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 :

Copier le code Le code est le suivant :

var a = Array(1, 2, 3); // [objet Array]
var b = new Array(1, 2, 3); // [objet Array]
var c = [1, 2, 3]; // [objet Tableau]

var d = Fonction(''); // [objet Fonction]
var e = new Function(''); // [objet Fonction]

Lorsque certains opérateurs sont utilisés, il existe également des conversions explicites et implicites :
Copier le code Le code est le suivant :

var a = 1;
varb = 2;

// Implicite
var c = a b; // 3, nombre
var d = a b '5' // "35", chaîne

// explicite
var e = '10'; // "10", chaîne
var f = e; // 10, nombre
var g = parseInt(e, 10); // 10, nombre

// Attends

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.

Copier le code Le code est le suivant :

var foo = {};

Object.defineProperty(foo, "x", {
valeur : 10,
inscriptible : true, // c'est-à-dire {ReadOnly} = false
enumerable : false, // c'est-à-dire {DontEnum} = true
configurable : true // c'est-à-dire {DontDelete} = false
});

console.log(foo.x); // 10

// Récupère les attributs de l'ensemble de fonctionnalités
via le descripteur var desc = Object.getOwnPropertyDescriptor(foo, "x");

console.log(desc.enumerable); // faux
console.log(desc.writable); // vrai
// Attends

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 :

Copier le code Le code est le suivant :

var getClass = Object.prototype.toString;

getClass.call({}); // [objet Objet]
getClass.call([]); // [tableau d'objets]
getClass.call(new Number(1)); // [numéro d'objet]
// Attends

Cette fonction est généralement utilisée pour vérifier des objets, mais la spécification indique que la [[Class]] de l'objet hôte peut être n'importe quelle valeur, y compris la valeur de l'attribut [[Class]] de l'objet intégré, donc théoriquement, elle ne peut pas être garanti à 100 %. Par exemple, l'attribut [[Class]] de la méthode document.childNodes.item(...) renvoie "String" dans IE, mais il renvoie "Function" dans d'autres implémentations.
Copier le code Le code est le suivant :

// dans IE - "String", dans autre - "Fonction"
alert(getClass.call(document.childNodes.item));

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) :

Copier le code Le code est le suivant :

fonction A() {
// Mettre à jour l'objet nouvellement créé
ceci.x = 10;
// Mais il renvoie un objet différent
retourner [1, 2, 3];
>

var a = nouveau A();
console.log(a.x, a); non défini, [1, 2, 3]

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).

Copier le code Le code est le suivant :

F = nouveau NativeObject();

F.[[Classe]] = "Fonction"

.... // Autres attributs

F.[[Appel]] = // fonction elle-même

F.[[Construct]] = internalConstructor // Constructeur interne ordinaire

.... // Autres attributs

// Prototype d'objet créé par le constructeur F
__objectPrototype = {};
__objectPrototype.constructor = F // {DontEnum}
F.prototype = __objectPrototype

[[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 :
.

Copier le code Le code est le suivant :

// Dans le navigateur IE - "Objet", "objet", autres navigateurs - "Fonction", "fonction"
alert(Object.prototype.toString.call(window.alert));
alert(typeof window.alert); // "Objet"

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 :

Copier le code Le code est le suivant :

function A(x) { // constructeur А
ceci.x = x || 10;
>

// Si aucun paramètre n'est passé, les parenthèses peuvent être omises
var a = nouveau A; // ou nouveau A();
alert(a.x); // 10

//Passer explicitement le paramètre x
var b = nouveau A(20);
alert(b.x); // 20

Nous savons également que shis dans le constructeur (phase d'initialisation) est défini sur l'objet nouvellement créé.

É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 :

Copier le code Le code est le suivant :

F.[[Construct]](paramètres initiaux) :

O = nouveau NativeObject();

// La propriété [[Class]] est définie sur "Object"
O.[[Classe]] = "Objet"

// Récupère l'objet g
lors du référencement de F.prototype var __objectPrototype = F.prototype;

// Si __objectPrototype est un objet, alors :
O.[[Prototype]] = __objectPrototype
// Sinon :
O.[[Prototype]] = Objet.prototype;
// Ici O.[[Prototype]] est le prototype de l'objet Object

// F.[[Call]]
est appliqué lors de l'initialisation de l'objet nouvellement créé. // Définissez ceci sur l'objet nouvellement créé O
//Les paramètres sont les mêmes que les paramètres initiaux dans F
R = F.[[Appel]](paramètres initiaux); this === O;
// Ici R est la valeur de retour de [[Call]]
// Visualisez-le en JS, comme ceci :
// R = F.apply(O, initialParameters);

// Si R est un objet
retourner R
// Sinon
retourner O

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 :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = 10;

var a = nouveau A();
alert(a.x); // 10 – obtenir
du prototype
// Définit la propriété .prototype sur le nouvel objet
// Pourquoi déclarer explicitement la propriété .constructor est expliqué ci-dessous
A.prototype = {
constructeur : A,
y : 100
};

var b = nouveau A();
// L'objet "b" a de nouvelles propriétés
alert(b.x); // non défini
alert(by); // 100 – obtenir
du prototype
// Mais le prototype d'un objet peut toujours obtenir le résultat original
alert(a.x); // 10 - obtenir
du prototype
fonction B() {
ceci.x = 10;
renvoyer un nouveau tableau();
>

// Si le constructeur "B" ne renvoie pas (ou renvoie ceci)
// Alors cet objet peut être utilisé, mais dans le cas suivant, array
est renvoyé var b = nouveau B();
alert(b.x); // non défini
alert(Object.prototype.toString.call(b)); // [object Array]

Regardons de plus près le prototype

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 :

Copier le code Le code est le suivant :

fonction A() {}
var a = nouveau A();
alert(a.constructor); // fonction A() {}, par délégation
alert(a.constructor === A); // true

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 :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = nouveau numéro (10);

var a = nouveau A();
alert(a.constructor.prototype); // [objet Objet]

alert(a.x); // 10, via prototype
// Même effet que a.[[Prototype]].x
alert(a.constructor.prototype.x); // 10

alert(a.constructor.prototype.x === a.x); // true

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 :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype = {
x : 10
};

var a = nouveau A();
alert(a.x); // 10
alert(a.constructor === A); // faux !

Par conséquent, la référence du prototype à la fonction doit être restaurée manuellement :
Copier le code Le code est le suivant :

fonction A() {}
A.prototype = {
constructeur : A,
x : 10
};

var a = nouveau A();
alert(a.x); // 10
alert(a.constructor === A); // true

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]].

Copier le code Le code est le suivant :

var foo = {x : 10};

Object.defineProperty(foo, "y", {
valeur : 20,
enumerable : false // alias {DontEnum} = true
});

console.log(foo.x, foo.y); // 10, 20

pour (var k dans foo) {
console.log(k); // seulement "x"
>

var xDesc = Object.getOwnPropertyDescriptor(foo, "x");
var yDesc = Object.getOwnPropertyDescriptor(foo, "y");

console.log(
xDesc.enumerable, // vrai
yDesc.enumerable // faux
);

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é).

Copier le code Le code est le suivant :

// La situation avant de modifier le prototype A.prototype
a.[[Prototype]] ----> Prototype <---- A.prototype

// Après modification
A.prototype ----> Nouveau prototype // Les nouveaux objets auront ce prototype
a.[[Prototype]] ----> Prototype // Démarrez le prototype original

Par exemple :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = 10;

var a = nouveau A();
alert(a.x); // 10

A.prototype = {
constructeur : A,
x : 20
y : 30
};

// L'objet a est une valeur obtenue à partir du prototype de pétrole brut via la référence implicite [[Prototype]]
alert(a.x); // 10
alerte(a.y) // non défini

var b = nouveau A();

// Mais le nouvel objet est la valeur obtenue du nouveau prototype
alert(b.x); // 20
alerte(par.y) // 30

Par conséquent, certains articles disent que "la modification dynamique du prototype affectera tous les objets et que tous les objets auront de nouveaux prototypes", ce qui est faux. Le nouveau prototype ne prendra effet sur les objets nouvellement créés qu'après la modification du prototype.

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 :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = 10;

var a = nouveau A();
alert(a.x); // 10

var __newPrototype = {
constructeur : A,
x : 20,
y : 30
};

//Référence au nouvel objet
A.prototype = __newPrototype;

var b = nouveau A();
alert(b.x); // 20
alert(by); // 30

// L'objet "a" utilise toujours l'ancien prototype
alert(a.x); // 10
alert(a.y); // non défini

// Modifier explicitement le prototype
a.__proto__ = __newPrototype;

// Désormais, l'objet "а" fait référence au nouvel objet
alert(a.x); // 20
alert(a.y); // 30

Notez qu'ES5 fournit la méthode Object.getPrototypeOf(O), qui renvoie directement la propriété [[Prototype]] de l'objet - le prototype initial de l'instance. Cependant, comparé à __proto__, il ne s'agit que d'un getter et n'autorise pas de valeurs définies.
Copier le code Le code est le suivant :

var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // true

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]] :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = 10;

var a = nouveau A();
alert(a.x); // 10

// Définit A sur null - affiche le constructeur de référence
A = nul;

// Mais si la propriété .constructor n'a pas changé,
// Vous pouvez toujours créer des objets grâce à lui
var b = nouveau a.constructor();
alert(b.x); // 10

// Les références implicites sont également supprimées
supprimer a.constructor.prototype.constructor ;
supprimer b.constructor.prototype.constructor;

// Les objets ne peuvent plus être créés via le constructeur de A
// Mais ces deux objets ont quand même leurs propres prototypes
alert(a.x); // 10
alert(b.x); // 10

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 :

Copier le code Le code est le suivant :

if (foo instanceof Foo) {
...
>

Ceci n'est pas utilisé pour détecter si l'objet foo a été créé à l'aide du constructeur Foo. Tous les opérateurs instanceof ne nécessitent qu'une seule propriété d'objet - foo.[[Prototype]], et vérifient son existence à partir de Foo.prototype dans la chaîne de prototypes. L'opérateur instanceof est activé via la méthode interne [[HasInstance]] dans le constructeur.

Regardons cet exemple :

Copier le code Le code est le suivant :

fonction A() {}
A.prototype.x = 10;

var a = nouveau A();
alert(a.x); // 10

alert(une instance de A); // true

// Si le prototype est défini sur null
A.prototype = nul;

//..."a" peut toujours accéder au prototype via a.[[Prototype]]
alert(a.x); // 10

// Cependant, l'opérateur instanceof ne peut plus être utilisé normalement
// Parce qu'il est implémenté à partir de l'attribut prototype du constructeur
alert(une instance de A); // Erreur, A.prototype n'est pas un objet

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é :

Copier le code Le code est le suivant :

fonction B() {}
var b = nouveau B();

alert(b instanceof B); // vrai

fonction C() {}

var __proto = {
constructeur : C
};

C.prototype = __proto;
b.__proto__ = __proto;

alert(b instanceof C); // vrai
alert(b instanceof B); // faux

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.

Copier le code Le code est le suivant :

fonction A(x) {
ceci.x = x || 100;
>

A.prototype = (fonction () {

// Initialiser le contexte
// Utiliser des objets supplémentaires

var _someSharedVar = 500;

fonction _someHelper() {
alert('assistant interne : ' _someSharedVar);
>

fonction méthode1() {
alert('method1: 'this.x);
>

fonction méthode2() {
alert('method2: 'this.x);
_someHelper();
>

// Prototype lui-même
Retour {
​ constructeur : A,
Méthode1 : méthode1,
Méthode2 : méthode2
};

})();

var a = nouveau A(10);
var b = nouveau A(20);

a.method1(); // méthode1 : 10
a.method2(); // méthode2 : 10, assistant interne : 500

b.method1(); // méthode1 : 20
b.method2(); // méthode2 : 20, assistant interne : 500

// Les deux objets utilisent la même méthode dans le prototype
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // vrai

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 :

Copier le code Le code est le suivant :

// écris
foo.bar = 10; // Appelé [[Put]]

console.log(foo.bar); // 10, appelé [[Get]]
console.log(foo['bar']); // Même effet

Voyons comment fonctionnent ces méthodes en pseudocode :

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):

Copier le code Le code est le suivant :

// S'il s'agit de votre propre attribut, retournez
if (O.hasOwnProperty(P)) {
Retour O.P ;
>

// Sinon, continuez à analyser le prototype
var __proto = O.[[Prototype]];

// Si le prototype est nul, renvoie undefined
// C'est possible : le Object.prototype.[[Prototype]] de niveau supérieur est nul
si (__proto === null) {
Retour indéfini ;
>

// Sinon, appelez [[Get]] récursivement sur la chaîne de prototypes et recherchez les attributs dans les prototypes de chaque couche
// Jusqu'à ce que le prototype soit nul
retourner __proto.[[Get]](P)

Veuillez noter que [[Get]] renverra également undéfini dans les situations suivantes :
Copier le code Le code est le suivant :

si (window.someObject) {
...
>

Ici, si la propriété someObject n'est pas trouvée dans la fenêtre, elle sera recherchée dans le prototype, puis dans le prototype du prototype, et ainsi de suite. Si elle est introuvable, undefined sera renvoyé selon la définition.

Remarque : L'opérateur in peut également être chargé de rechercher des propriétés (il recherchera également la chaîne de prototypes) :

Copier le code Le code est le suivant :

if ('someObject' dans la fenêtre) {
...
>

Cela permet d'éviter certains problèmes particuliers : par exemple, même si someObject existe, lorsque someObject est égal à false, le premier cycle de détection échouera.

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):

Copier le code Le code est le suivant :

// Si la valeur ne peut pas être écrite dans l'attribut, quittez
if (!O.[[CanPut]](P)) {
Retour ;
>

// Si l'objet n'a pas ses propres propriétés, créez-le
// Tous les attributs sont faux
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributs : {
Lecture seule : faux,
DontEnum : faux,
DontDelete : faux,
Interne : faux
});
>

// Définit la valeur si l'attribut existe, mais ne modifie pas la propriété des attributs
O.P = V

revenir ;

Par exemple :
Copier le code Le code est le suivant :

Objet.prototype.x = 100;

var foo = {};
console.log(foo.x); // 100, propriétés héritées

foo.x = 10; // [[Put]]
console.log(foo.x); // 10, propres attributs

supprimer foo.x;
console.log(foo.x); //réinitialisé à 100, hérite des propriétés
Veuillez noter que les propriétés en lecture seule dans le prototype ne peuvent pas être masquées et que les résultats de l'affectation seront ignorés. Ceci est contrôlé par la méthode interne [[CanPut]].

// 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


Mais dans le mode strict d'ES5, si l'attribut en lecture seule est masqué, une TypeError sera enregistrée.

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.

Copier le code Le code est le suivant :

var a = {testProperty: 10};

alert(a.testProperty); // 10, cliquez sur
alert(a['testProperty']); // 10, index

var propertyName = 'Propriété';
alert(a['test' propertyName]); // 10, les propriétés dynamiques sont indexées

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 :

Copier le code Le code est le suivant :

var a = 10; // Valeur originale

// Mais les méthodes sont accessibles (tout comme les objets)
alert(a.toString()); // "10"

// De plus, nous pouvons créer un attribut coeur sur un
a.test = 100; // Cela ne semble pas poser de problème

// Cependant, la méthode [[Get]] ne renvoie pas la valeur de la propriété, mais renvoie undefined
alert(a.test); // non défini

Alors, pourquoi la valeur d'origine dans l'ensemble de l'exemple peut-elle avoir accès à la méthode toString, mais pas à la propriété test nouvellement créée ?

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 :

Copier le code Le code est le suivant :

//Principe d'exécution de a.toString() :

1. wrapper = nouveau Nombre(a);
2. wrapper.toString(); // "10"
3. supprimer le wrapper ;

Ensuite, lorsque la méthode [[Put]] crée de nouveaux attributs, cela se fait également via l'objet packagé :
Copier le code Le code est le suivant :

//Le principe d'exécution de a.test = 100 :

1. wrapper = nouveau Nombre(a);
2. wrapper.test = 100;
3. supprimer le wrapper ;

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é :

Copier le code Le code est le suivant :

//Principe d'exécution de a.test :

1. wrapper = nouveau Nombre(a);
2. wrapper.test; // non défini

Cette méthode explique comment la valeur originale est lue. De plus, si une valeur originale est souvent utilisée pour accéder aux attributs, des considérations d'efficacité temporelle la remplaceront directement par un objet au contraire, si elle n'est pas consultée fréquemment, ou simplement utilisée pour ; à des fins de calcul, ce formulaire peut être conservé.

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 :

Copier le code Le code est le suivant :

alert(1..toString()); // "1"

Nous savons déjà comment fonctionnent la méthode [[Get]] et les accesseurs de propriétés, voyons ce qui se passe :

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 :

Copier le code Le code est le suivant :

1.toString(); // Erreur de syntaxe !

(1).toString(); // OK

1..toString(); // OK

1['toString'](); // OK

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 :

Copier le code Le code est le suivant :

fonction A() {
alert('A.[[Appel]] activé');
ceci.x = 10;
>
A.prototype.y = 20;

var a = nouveau A();
alert([a.x, a.y]); // 10 (soi), 20 (hérité)

fonction B() {}

// La méthode de chaîne de prototypes la plus récente consiste à définir le prototype de l'objet sur un autre nouvel objet
B.prototype = nouveau A();

// Répare la propriété constructeur du prototype, sinon ce serait A
B.prototype.constructor = B;

var b = nouveau B();
alert([b.x, b.y]); // 10, 20, 2 sont hérités

// [[Obtenir]] b.x :
// b.x (non) --->
// b.[[Prototype]].x (oui) - 10

// [[Obtenir]] par.y
// par (non) --->
// b.[[Prototype]].y (non) -->
// b.[[Prototype]].[[Prototype]].y (oui) - 20

// où b.[[Prototype]] === B.prototype,
// et b.[[Prototype]].[[Prototype]] === A.prototype

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.

Copier le code Le code est le suivant :

fonction A(param) {
si (!param) {
lancer 'Param requis' ;
>
this.param = param;
>
A.prototype.x = 10;

var a = nouveau A(20);
alert([a.x, a.param]); // 10, 20

fonction B() {}
B.prototype = new A(); // Erreur

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.

Copier le code Le code est le suivant :

fonction A() {
alert('A.[[Appel]] activé');
ceci.x = 10;
>
A.prototype.y = 20;

var a = nouveau A();
alert([a.x, a.y]); // 10 (auto), 20 (intégré)

fonction B() {
// Ou utilisez A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
>

// Héritage : connectez les prototypes entre eux via des constructeurs intermédiaires vides
var F = fonction () {};
F.prot
Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn