introduire
Ce chapitre est le deuxième chapitre sur la mise en œuvre orientée objet ECMAScript. Dans le premier chapitre, nous discutons de la comparaison entre l'introduction et le Cemascript. Si vous n'avez pas lu le premier chapitre, avant de poursuivre ce chapitre, je vous recommande fortement de lire le premier chapitre en premier, car ce chapitre est trop long (page 35).
Texte original de l'anglais: http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Remarque: Parce que l'article est trop long, les erreurs sont inévitables et elles sont constamment corrigées.
Dans l'introduction, nous nous étendons à Ecmascript. Maintenant, lorsque nous savons que c'est la mise en œuvre de la POO, définissons-le avec précision:
La copie de code est la suivante:
ECMascript est un langage de programmation orienté objet soutenant la déléguation de l'héritage basé sur des prototypes.
ECMAScript est un langage orienté objet qui prend en charge l'héritage délégué basé sur un prototype.
Nous analyserons les types de données les plus élémentaires. La première chose que nous devons comprendre est qu'Ecmascript utilise des valeurs et des objets primitifs pour distinguer les entités. Par conséquent, le "en JavaScript, tout est un objet" mentionné dans certains articles est mauvais (pas complètement correct), et la valeur primitive est certains types de données dont nous allons discuter ici.
Type de données
Bien que ECMascript soit un langage de type faible dynamique qui peut convertir dynamiquement des types, il a 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 seulement 6 sont directement accessibles dans les programmes ECMAScript. Ils sont: indéfinis, nul, booléen, chaîne, nombre et objet.
Les trois autres types ne sont accessibles qu'au niveau de l'implémentation (ces types ne peuvent pas être utilisés par les objets ECMAScript) et sont utilisés pour des spécifications pour expliquer un comportement opérationnel et enregistrer des valeurs intermédiaires. Ces 3 types sont: référence, liste et achèvement.
Par conséquent, la référence est utilisée pour expliquer les opérateurs tels que la suppression, le typeof, et ceci, et contient un objet de base et un nom d'attribut; La liste décrit le comportement de la liste des paramètres (lorsque de nouvelles expressions et appels de fonction); L'achèvement est utilisé pour expliquer les déclarations de pause, de poursuite, de retour et de lancer.
Type de valeur primitive
En regardant en arrière les types de données utilisés dans les programmes ECMAScript dans 6, les 5 premiers sont des types de valeur primitifs, notamment un non défini, nul, booléen, chaîne, nombre et objet.
Exemple de type de valeur d'origine:
La copie de code est la suivante:
var a = non défini;
var b = null;
var c = true;
var d = 'test';
var e = 10;
Ces valeurs sont implémentées directement à la couche inférieure, ce ne sont pas des objets, il n'y a donc pas de prototype, pas de constructeur.
Oncle Remarque: Bien que ces valeurs natives soient similaires à celles que nous utilisons habituellement (booléen, chaîne, nombre, objet) mais ne sont pas la même chose. Par conséquent, les résultats du typeof (true) et du typeof (booléen) sont différents, car le résultat du typeof (booléen) est une fonction, de sorte que les fonctions booléen, chaîne et nombre ont des prototypes (le chapitre des attributs de lecture et d'écriture suivants sera également mentionné).
Si vous souhaitez savoir quel type de données est le meilleur, il est préférable d'utiliser le type de feu. Il y a un exemple qui doit être noté. Si vous utilisez le typeof pour juger le type de NULL, le résultat est un objet. Pourquoi? Parce que le type de null est défini comme nul.
La copie de code est la suivante:
alerte (typeof null); // "objet"
La raison de l'affichage de "l'objet" est que la spécification stipule ceci: renvoie "Object" pour la valeur de chaîne type de la valeur nul.
La spécification n'imagine pas l'expliquer, mais Brendan Eich (inventeur de JavaScript) a remarqué que Null est principalement utilisé pour que les objets apparaissent par rapport à des non définis, comme la définition d'un objet sur une référence nul. Cependant, certains documents ont un peu ennuyeux pour l'attribuer à un bogue et mettre le bug dans la liste des bogues que Brendan Eich a également participé à la discussion. Le résultat est que si vous le laissez partir, vous devez définir le résultat du type de null à l'objet (bien que la norme 262-3 définisse le type de NULL comme null et que le 262-5 ait changé la norme en NULL en tant qu'objet).
Type d'objet
Ensuite, le type d'objet (à ne pas confondre avec le constructeur d'objets, maintenant seul le type abstrait est discuté) est le seul type de données qui décrit l'objet ECMascript.
L'objet est une collection non ordonnée de paires de valeurs clés.
L'objet est une collection non ordonnée contenant des paires de valeurs clés
La valeur clé d'un objet est appelée propriété et la propriété est un conteneur de la valeur d'origine et d'autres objets. Si la valeur d'une propriété est une fonction, nous l'appelons une méthode.
Par exemple:
La copie de code est la suivante:
var x = {// L'objet "x" a 3 propriétés: a, b, c
R: 10, // Valeur d'origine
b: {z: 100}, // L'objet "b" a un attribut z
c: function () {// function (méthode)
alert ('méthode x.c');
}
};
alerte (xa); // 10
alerte (xb); // [objet objet]
alerte (xbz); // 100
xc (); // "Méthode x.c '
Dynamique
Comme nous l'avons souligné dans le chapitre 17, les objets en ES sont complètement dynamiques. Cela signifie que lorsque le programme est exécuté, nous pouvons ajouter, modifier ou supprimer les propriétés de l'objet à volonté.
Par exemple:
La copie de code est la suivante:
var foo = {x: 10};
// ajouter de nouveaux attributs
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// modifie la valeur de la propriété à une fonction
foo.x = fonction () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Supprimer les attributs
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 dans les caractéristiques d'attribut ultérieurement.
De plus, la spécification ES5 stipule que les objets statiques ne peuvent pas étendre de nouveaux attributs, et leurs pages de propriété ne peuvent pas être supprimées ou modifiées. Ce sont des objets dits congelés, qui peuvent être obtenus en appliquant la méthode objet.freeze (o).
La copie de code est la suivante:
var foo = {x: 10};
// geler l'objet
Object.freeze (foo);
console.log (object.isfrozen (foo)); // vrai
// ne peut pas être modifié
foo.x = 100;
// ne peut pas se développer
foo.y = 200;
// ne peut pas être supprimé
supprimer foo.x;
console.log (foo); // {x: 10}
Dans la spécification ES5, la méthode Object.Pretentextensions (O) est également utilisée pour prévenir les extensions, ou la méthode objet.DefineProperty (O) est utilisée pour définir les propriétés:
La copie de code est la suivante:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Valeur: 20,
Writable: False, // Lire uniquement
configurable: faux // non configurable
});
// ne peut pas être modifié
foo.y = 200;
// ne peut pas être supprimé
supprimer foo.y; // FAUX
// Expansion de prévention et de contrôle
Object.preventextensions (foo);
console.log (object.isextenible (foo)); // FAUX
// ne peut pas ajouter de nouveaux attributs
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Objets intégrés, objets natifs et objets hôte
Il est nécessaire de noter que la spécification distingue également ces objets intégrés, objets d'élément et objets hôte.
Les objets intégrés et les objets d'élément sont définis et implémentés par la spécification ECMAScript, et la différence entre les deux est triviale. Tous les objets ECMAScript implémentant sont des objets natifs (dont certains sont des objets intégrés, certains sont créés lorsque le programme est exécuté, tels que les objets définis par l'utilisateur). Un objet intégré est un sous-ensemble de l'objet natif et est intégré à ECMAScript avant le début du programme (par exemple, ParseInt, Match, etc.). Tous les objets hôtes sont fournis par l'environnement hôte, généralement un navigateur, et peuvent inclure, par exemple, une fenêtre, une alerte, etc.
Notez que l'objet hôte peut être mis en œuvre par ES lui-même et se conforme pleinement à la sémantique normative. À cet égard, ils peuvent être appelés objets "hôte natif" (dès que possible), mais la norme ne définit pas le concept d'objets "hôte natif".
Objets booléens, string et numéro
De plus, la spécification définit également certaines classes de wrapper spéciales natives, ces objets sont:
1. Objet booléen
2. Objet String
3. Objets numériques
Ces objets sont créés par le constructeur intégré correspondant et contiennent des valeurs natives comme propriétés internes. Ces objets peuvent être convertis pour enregistrer la valeur d'origine et vice versa.
La copie de code est la suivante:
var c = new boolean (true);
var d = new String ('test');
var e = nouveau nombre (10);
// Convertir en valeur d'origine
// Utiliser des fonctions sans nouveau mot-clé
с = booléen (c);
D = String (D);
e = nombre (e);
// Reconnut à l'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: fonction (constructeur d'objets de fonction), array (constructeur de tableaux), regexp (constructeur d'expression régulière), mathématiques (module mathématique), date (constructeur de dattes), etc. Ces objets sont également des valeurs de types d'objets d'objet. Leurs différences sont gérées par des propriétés internes. Nous discuterons de ces choses ci-dessous.
Littéral
Pour les valeurs de trois objets: objet, tableau et expression régulière, ils ont des identificateurs abrégés appelés initialiseur d'objet, initialiseur de tableau et initialiseur d'expression régulière:
La copie de code est la suivante:
// équivalent à un nouveau tableau (1, 2, 3);
// ou array = new Array ();
// Array [0] = 1;
// Array [1] = 2;
// Array [2] = 3;
Var Array = [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 à un nouveau regexp ("^ // d + $", "g")
var re = / ^ / d + $ / g;
Notez que si les trois objets ci-dessus sont réaffectés à un nouveau type, la sémantique d'implémentation suivante est utilisée en fonction du nouveau type. Par exemple, dans le Rhino actuel et l'ancienne version de SpiderMonkey 1.7, les objets seront créés avec succès avec le constructeur du nouveau mot-clé, mais la sémantique de certaines implémentations (les littéraux actuels / tracemonkey) peuvent ne pas changer après le changement du type.
La copie de code est la suivante:
var getClass = object.prototype.toString;
Object = nombre;
var foo = nouvel objet;
alert ([foo, getClass.Call (foo)]); // 0, "[numéro d'objet]"
var bar = {};
// Rhino, SpiderMonkey 1.7- 0, "[Numéro d'objet]"
// autres: toujours "[objet objet]", "[objet objet]"
alert ([bar, getClass.Call (bar)]);
// Array a le même effet
Array = numéro;
foo = nouveau tableau;
alert ([foo, getClass.Call (foo)]); // 0, "[numéro d'objet]"
bar = [];
// Rhino, SpiderMonkey 1.7- 0, "[Numéro d'objet]"
// Autres: Toujours "", "[objet objet]"
alert ([bar, getClass.Call (bar)]);
// Mais pour l'expression regexp, la sémantique des littéraux n'est pas modifiée. sémantique du littéral
// n'est pas modifié dans toutes les implémentations testées
Regexp = nombre;
foo = new regexp;
alert ([foo, getClass.Call (foo)]); // 0, "[numéro d'objet]"
bar = / (?!) / g;
alert ([bar, getClass.Call (bar)]); // / (?!) / g, "[objet regexp]"
Literals regex et objets regexp
Notez que dans les deux exemples suivants, dans la spécification de la troisième édition, la sémantique des expressions régulières est équivalente. Les littéraux regexp n'existent que dans une phrase et sont créés au stade d'analyse. Cependant, le constructeur regexp crée un nouvel objet, donc cela peut causer certains problèmes, tels que la valeur de LastIndex est erronée pendant les tests:
La copie de code est la suivante:
pour (var k = 0; k <4; k ++) {
var re = / ecma / g;
alert (re.lastindex); // 0, 4, 0, 4
alert (re.test ("ecmascript")); // vrai, faux, vrai, faux
}
// Comparer
pour (var k = 0; k <4; k ++) {
var re = new regexp ("ecma", "g");
alert (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 spécification ES dans la 5e édition. Peu importe qu'ils soient basés sur des littéraux ou des constructeurs, ils créent de nouveaux objets.
Tableau associatif
Diverses discussions statiques sur le texte, les objets JavaScript (souvent créés avec l'initialisateur d'objets {}) sont appelés tables de hachage et tables de hachage ou autres titres simples: hachage (concepts en rubis ou perl), tableaux de gestion (concepts en php), dictionnaires (concepts en python), etc.
Il n'y a que de tels termes, principalement parce que leurs structures sont similaires, c'est-à-dire en utilisant des paires de "valeur clé" pour stocker des objets, qui se conforment pleinement à la structure de données définie par la théorie "Array d'association" ou "Table de hachage". De plus, les types de données abstraits de table de hachage sont généralement utilisés au niveau de la mise en œuvre.
Cependant, bien que le concept soit décrit en termes de termes, il s'agit en fait d'une erreur. Du point de vue de ECMascript: ECMascript n'a qu'un seul objet, type et son sous-type, qui n'est pas différent de la "valeur clé" au stockage, il n'y a donc pas de concept spécial à ce sujet. Parce que les propriétés internes de tout objet peuvent être stockées sous forme de «paires de valeurs clés:
La copie de code est la suivante:
var a = {x: 10};
a ['y'] = 20;
az = 30;
var b = nouveau nombre (1);
bx = 10;
par = 20;
b ['z'] = 30;
var c = nouvelle fonction ('');
cx = 10;
cy = 20;
c ['z'] = 30;
// etc., sous-type de tout objet "sous-type"
De plus, comme les objets peuvent être vides dans ECMAScript, le concept de "hachage" est également incorrect ici:
La copie de code est la suivante:
Object.prototype.x = 10;
var a = {}; // Créer un "hash" vide
alert (a ["x"]); // 10, mais pas vide
alerte (a.tostring); // fonction
a ["y"] = 20; // Ajouter une nouvelle paire de valeurs clés à "hachage"
alerte (a ["y"]); // 20
Object.prototype.y = 20; // ajouter des attributs prototypes
supprimer un ["y"]; // Supprimer
alerte (a ["y"]); // mais ici la clé et la valeur valent toujours 20
Veuillez noter que la norme ES5 nous permet de créer des objets non non plus (implémentés à l'aide de la méthode object.create (null)). De ce point de vue, ces objets peuvent être appelés tables de hachage:
La copie de code est la suivante:
var ahashTable = object.create (null);
Console.log (AhashTable.ToString); // indéfini
De plus, certaines propriétés ont des méthodes spécifiques de Getter / Setter, donc cela peut également entraîner une confusion sur ce concept:
La copie de code est la suivante:
var a = new String ("foo");
a ['longueur'] = 10;
alerte (a ['longueur']); // 3
Cependant, même si le "hachage" pourrait avoir un "prototype" (par exemple, une classe qui délégue des objets de hachage dans Ruby ou Python), ce terme n'est pas correct dans ECMAScript car il n'y a pas de différence sémantique entre les deux notations (c'est-à-dire la notation de points AB et une notation ["B"]).
Le concept de «l'attribut de propriété» dans ECMAScript n'est pas sémantiquement séparé de la «clé», de l'indice du tableau et des méthodes. Ici, toute la lecture et l'écriture d'attributs d'objets doivent suivre une règle unifiée: consultez la chaîne prototype.
Dans l'exemple de rubis suivant, nous pouvons voir la différence sémantique:
La copie de code est la suivante:
a = {}
a.class # hachage
A.Length # 0
# Nouvelle paire "Key-Value"
a ['longueur'] = 10;
# Sémantiquement, l'attribut ou la méthode accessible avec un point, pas une clé
A.Length # 1
# L'indexeur accède à la clé du hachage
A ['longueur'] # 10
# C'est similaire à la déclaration dynamiquement d'une classe de hachage sur un objet existant
# Alors déclarez le nouvel attribut ou méthode
hachage de classe
def z
100
fin
fin
# Les nouvelles propriétés sont accessibles
AZ # 100
# Mais pas "clé"
a ['z'] # nil
La norme ECMA-262-3 ne définit pas le concept de "hachage" (et similaire). Cependant, s'il existe une telle théorie structurelle, l'objet nommé d'après cela peut être.
Conversion d'objet
Pour convertir un objet en une valeur primitive, vous pouvez utiliser la méthode de valeur de la valeur. Comme nous l'avons dit, lorsque le constructeur de la fonction est appelé comme une fonction (pour certains types), mais si vous n'utilisez pas le nouveau mot-clé, vous convertissez l'objet en valeur d'origine, il est équivalent en une valeur implicite de l'appel de méthode:
La copie de code est la suivante:
var a = nouveau nombre (1);
var primitivea = nombre (a); // appel "valeur" implicite
var alsoprimitivea = a.valueof (); // appel explicite
alerte([
type de a, // "objet"
Type de primitivea, // "numéro"
Typeof alsoprimitivea // "numéro"
]));
Cette méthode permet aux objets de participer à diverses opérations, telles que:
La copie de code est la suivante:
var a = nouveau nombre (1);
var b = nouveau nombre (2);
alerte (a + b); // 3
// même
var c = {
X: 10,
Y: 20,
Value Of: function () {
renvoyer ce.x + this.y;
}
};
var d = {
X: 30,
Y: 40,
// la même fonction que la valeur de C
valeur de: c.valueof
};
alerte (c + d); // 100
La valeur par défaut de la valeur de la valeur changera en fonction du type de l'objet (si ce n'est pas remplacé). Pour certains objets, il le renvoie - par exemple: object.prototype.valueof (), ainsi que la valeur calculée: date.prototype.valueof () Renvoie la date et l'heure:
La copie de code est la suivante:
var a = {};
alerte (a.valueof () === a); // vrai, "Valeur" renvoie ceci
var d = new Date ();
alerte (d.Valueof ()); // temps
alert (d.Valueof () === D.GetTime ()); // vrai
De plus, l'objet a une représentation plus primitive - affichage de chaîne. Cette méthode de tostring est fiable et elle est utilisée automatiquement dans certaines opérations:
La copie de code est la suivante:
var a = {
Value Of: function () {
retour 100;
},
toString: function () {
retour '__test';
}
};
// Dans cette opération, la méthode TOSTRING est automatiquement appelée
alerte (a); // "__test"
// mais ici, la méthode de valeur de () est appelée
alerte (A + 10); // 110
// Cependant, une fois la valeur de la suppression
// tostring peut être appelé à nouveau automatiquement
supprimer a.valueof;
alerte (A + 10); // "_test10"
La méthode TOSTRING définie sur Object.Protype a une signification particulière, et il renvoie la valeur d'attribut interne [[classe]] dont nous discuterons ci-dessous.
Par rapport à la conversion en valeurs d'origine (toprimitives), il existe également une spécification de conversion (TOOBject) pour convertir les valeurs en types d'objets.
Une méthode explicite consiste à utiliser le constructeur d'objets intégré comme une fonction pour appeler ToObject (quelque chose comme l'utilisation du nouveau mot-clé est OK):
La copie de code est la suivante:
var n = objet (1); // [numéro d'objet]
var s = objet ('test'); // [String d'objet]
// Certaines similitudes sont également possibles avec le nouvel opérateur
var b = nouvel objet (true); // [objet boolean]
// Si vous appliquez le nouvel objet du paramètre, vous créez un objet simple
var o = nouvel objet (); // [objet objet]
// Si le paramètre est un objet existant
// Le résultat de la création est de renvoyer l'objet
var a = [];
alert (a === nouvel objet (a)); // vrai
alert (a === objet (a)); // vrai
En ce qui concerne l'appel des constructeurs intégrés, il n'y a pas de règles communes à utiliser ou à ne pas appliquer le nouvel opérateur, selon le constructeur. Par exemple, le tableau ou la fonction utilise un constructeur du nouvel opérateur ou une fonction simple qui n'utilise pas le nouvel opérateur pour produire le même résultat:
La copie de code est la suivante:
var a = tableau (1, 2, 3); // [Array d'objets]
var b = nouveau tableau (1, 2, 3); // [Array d'objets]
var c = [1, 2, 3]; // [Array d'objets]
var d = fonction (''); // [fonction objet]
var e = nouvelle fonction (''); // [fonction objet]
Lorsque certains opérateurs sont utilisés, il y a aussi des conversions d'affichage et implicites:
La copie de code est la suivante:
var a = 1;
var b = 2;
// implicite
var c = a + b; // 3, numéro
var d = a + b + '5' // "35", chaîne
// explicite
var e = '10'; // "10", chaîne
var f = + e; // 10, numéro
var g = parseInt (e, 10); // 10, numéro
// etc
Propriétés des attributs
Toutes les propriétés peuvent avoir de nombreux attributs.
1. {readonly} - ignorer l'opération d'écriture de l'attribution de valeurs aux attributs, mais les attributs en lecture seule peuvent être modifiés par le comportement de l'environnement hôte - c'est-à-dire qu'ils ne sont pas des "valeurs constantes";
2. {dontenum} - L'attribut ne peut pas être énuméré par ... en boucle
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, sans nom (uniquement utilisé 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 [[écrivatif]], [[énumérable]] et [[configurable]], et ces propriétés peuvent être gérées manuellement via objet.defineproperty ou des méthodes similaires.
La copie de code est la suivante:
var foo = {};
Object.defineproperty (foo, "x", {
Valeur: 10,
Writable: true, // c'est-à-dire {readonly} = false
Énumérable: faux, // c'est-à-dire {dontenum} = true
configurable: true // c'est-à-dire {netDelete} = false
});
console.log (foo.x); // 10
// Obtenez les attributs à travers la description
var desc = object.getownpropertyDescriptor (foo, "x");
console.log (desc.enumable); // FAUX
console.log (desc.witable); // vrai
// etc
Propriétés et méthodes internes
Les objets peuvent également avoir des propriétés internes (une partie du niveau d'implémentation) et les programmes ECMAScript ne peuvent pas accéder directement (mais nous verrons ci-dessous que certaines implémentations permettent d'accéder à certaines de ces propriétés). Ces propriétés sont accessibles par des supports imbriqués [[]]. Regardons certaines de ces propriétés. La description de ces propriétés se trouve dans la spécification.
Chaque objet doit implémenter les propriétés et méthodes internes suivantes:
1. [[Prototype]] - Le prototype de l'objet (qui sera introduit en détail ci-dessous)
2. [[Classe]] - une représentation d'un objet de chaîne (par exemple, tableau d'objet, objet de fonction, fonction, etc.); utilisé pour distinguer les objets
3. [[Obtenir]] - la méthode pour obtenir des valeurs d'attribut
4. [[Put]] - la méthode de définition des valeurs d'attribut
5. [[CANPUT]] - Vérifiez si l'attribut est écrivable
6. [[Hasproperty]] - Vérifiez si l'objet a déjà cette propriété
7. [[Supprimer]] - Supprimer cette propriété de l'objet
8. [[DefaultValue]] Renvoie la valeur d'origine de l'objet pour (appeler la méthode de valeur OF et certains objets peuvent lancer une exception TypeError).
La valeur de la propriété interne [[classe]] peut être obtenue indirectement via la méthode object.prototype.tostring (), qui doit renvoyer la chaîne suivante: "[objet" + [[classe]] + "]". Par exemple:
La copie de code est la suivante:
var getClass = object.prototype.toString;
getClass.call ({}); // [objet objet]
getClass.Call ([]); // [Array d'objets]
getClass.Call (nouveau numéro (1)); // [numéro d'objet]
// etc
Cette fonction est généralement utilisée pour vérifier les objets, mais en spécification, la [classe]] de l'objet hôte peut être n'importe quelle valeur, y compris la valeur de l'attribut [[classe]] de l'objet intégré, donc théoriquement il ne peut pas être précis à 100%. Par exemple, la propriété [[classe]] de la méthode document.childnodes.item (...) renvoie "String" dans IE, mais la "fonction" renvoyée dans d'autres implémentations est en effet.
La copie de code est la suivante:
// dans IE - "String", dans d'autres - "fonction"
alert (getClass.Call (document.childnodes.item));
Constructeur
Ainsi, comme nous l'avons mentionné ci-dessus, les objets d'ECMascript sont créés par le biais de soi-disant 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 les objets nouvellement créés.
La création d'objets (allocation de mémoire) est la responsabilité de la méthode interne du constructeur [[construction]]. Le comportement de cette méthode interne est défini et tous les constructeurs utilisent cette méthode pour allouer la mémoire à de nouveaux objets.
L'initialisation est gérée en appelant la fonction de haut en bas du nouvel objet, qui est responsable par la méthode interne du constructeur [[appel]].
Notez que le code utilisateur ne peut être accessible que dans la phase d'initialisation, bien que nous puissions retourner différents objets pendant la phase d'initialisation (ignorer les objets TIHS créés dans la première phase):
La copie de code est la suivante:
fonction a () {
// Mette à jour les objets nouvellement créés
this.x = 10;
// mais le retour est un objet différent
retour [1, 2, 3];
}
var a = new a ();
console.log (AX, A); non défini, [1, 2, 3]
En se référant aux fonctions du chapitre 15 - Section d'algorithme pour la création de fonctions, nous pouvons voir que la fonction est un objet natif, y compris les propriétés [[construction]] et [[appel]]] ainsi que le prototype du prototype affiché - le prototype du futur objet (Remarque: NativeObject est une convention pour les objets d'objet natif, utilisés dans le pseudocode ci-dessous).
La copie de code est la suivante:
F = new nativeObject ();
F. [[Classe]] = "fonction"
.... // Autres attributs
F. [[Appel]] = <référence à la fonction> // fonction elle-même
F. [[Construction]] = Constructor interne // Constructeur interne ordinaire
.... // Autres attributs
// Prototype d'objet créé par F Constructeur
__ObjectPrototype = {};
__objectprototype.constructor = f // {dontenum}
F.prototype = __objectprototype
[[Call]]] est le principal moyen de distinguer les objets à l'exception de l'attribut [[classe]] (qui équivaut à "fonction" ici), donc l'attribut interne [[appel]] de l'objet est appelé en fonction. Si un tel objet utilise l'opérateur de type, il renvoie "fonction". Cependant, il est principalement lié aux objets natifs. Dans certains cas, la mise en œuvre utilise le type de type pour obtenir la valeur différemment, comme l'effet de Window.Alert (...) dans IE:
La copie de code est la suivante:
// Browser, "objet", "objet", autres navigateurs - "fonction", "fonction"
alert (object.prototype.tostring.call (Window.Alert));
alerte (typeof window. alert); // "Objet"
La méthode interne [[construction]] est activée en utilisant un constructeur avec un nouvel opérateur, comme nous l'avons dit, est responsable de l'allocation de la mémoire et de la création d'objets. S'il n'y a pas de paramètres, les supports pour appeler le constructeur peuvent également être omis:
La copie de code est la suivante:
fonction a (x) {// constructeur а
this.x = x || 10;
}
// Si vous ne transmettez pas de paramètres, les supports peuvent également être omis
var a = new a; // ou nouveau a ();
alerte (AX); // 10
// passe explicitement le paramètre x
var b = new a (20);
alerte (bx); // 20
Nous savons également que le SHIS dans le constructeur (phase d'initialisation) est défini sur l'objet nouvellement créé.
Étudions l'algorithme pour la création d'objets.
Algorithme de création d'objets
Le comportement de la méthode interne [[construction]] peut être décrit comme suit:
La copie de code est la suivante:
F. [[Construction]] (InitialParameters):
O = new nativeObject ();
// La propriété [[classe]] est définie sur "Object"
O. [[Classe]] = "objet"
// Obtenez l'objet G lorsque vous faites référence à 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 objet
// f. [[Appel]] est appliqué lorsque l'objet nouvellement créé est initialisé
// définit ce objet nouvellement créé o
// Les paramètres sont les mêmes que les paramètres initiaux en f
R = f. [[Appel]] (InitialParameters); ce === o;
// ici r est la valeur de retour de [[appel]]
// en js, comme ceci:
// r = f.apply (o, initialParameters);
// Si R est un objet
retour R
// Sinon
retour o
Veuillez noter deux fonctionnalités principales:
1. Premièrement, le prototype de l'objet nouvellement créé est obtenu à partir de la propriété prototype de la fonction pour le moment (cela signifie que les propriétés prototypes des deux objets créés créés par le même constructeur peuvent être différents car les propriétés prototypes de la fonction peuvent également être différentes).
2. Deuxièmement, comme nous l'avons mentionné ci-dessus, si [[appel]] renvoie l'objet lorsque l'objet est initialisé, c'est exactement le résultat utilisé pour l'ensemble du nouvel opérateur:
La copie de code est la suivante:
fonction a () {}
A.prototype.x = 10;
var a = new a ();
alerte (AX); // 10 Obtenez-le du prototype
// Définissez la propriété .prototype sur un nouvel objet
// Pourquoi l'attribut de construction explicitement déclaré sera expliqué ci-dessous
A.prototype = {
Constructeur: A,
Y: 100
};
var b = new a ();
// L'objet "B" a de nouvelles propriétés
alerte (bx); // indéfini
alerte (by); // 100 obtenez du prototype
// mais le prototype de l'objet A peut toujours obtenir le résultat d'origine
alerte (AX); // 10 - Get du prototype
fonction b () {
this.x = 10;
retourner un nouvel array ();
}
// Si le constructeur "B" ne revient pas (ou ne le retourne pas)
// alors cet objet peut être utilisé, mais la situation suivante renvoie un tableau
var b = new b ();
alerte (bx); // indéfini
alert (object.prototype.tostring.call (b)); // [Array d'objets]
Apprenez-en plus sur le prototype
prototype
Chaque objet a un prototype (sauf certains objets système). La communication prototype est effectuée par un accès interne, implicite et indirect aux propriétés du prototype [[prototype]]. Le prototype peut être un objet ou une valeur nul.
Constructeur de biens
L'exemple ci-dessus a deux points de connaissance importants. Le premier concerne la propriété prototype de la propriété constructeur de la fonction. Dans l'algorithme de la création de fonctions, nous savons que la propriété du constructeur est définie comme la propriété prototype de la fonction pendant l'étape de création de fonction. La valeur de la propriété du constructeur est une référence importante à la fonction elle-même:
La copie de code est la suivante:
fonction a () {}
var a = new a ();
alerte (A. Constructor); // fonction a () {}, par délégation
alerte (A. Constructor === A); // vrai
Habituellement, dans ce cas, il y a un malentendu: il est mal de construire une propriété comme l'objet nouvellement créé lui-même, mais, comme nous pouvons le voir, cette propriété appartient au prototype et accède à l'objet par l'héritage.
En héritant d'une instance de l'attribut constructeur, vous pouvez indirectement une référence à l'objet prototype:
La copie de code est la suivante:
fonction a () {}
A.prototype.x = nouveau numéro (10);
var a = new a ();
alert (a.constructor.prototype); // [objet objet]
alerte (AX); // 10, par prototype
// le même effet que a. [[Prototype]]. X
alert (a.constructor.prototype.x); // 10
alert (a.constructor.prototype.x === AX); // vrai
Mais veuillez noter que les propriétés du constructeur et du prototype de la fonction peuvent être redéfinies après la création de l'objet. Dans ce cas, l'objet perd le mécanisme mentionné ci-dessus. Si vous modifiez le prototype du prototype d'un élément via l'attribut prototype de la fonction (ajoutez de nouveaux objets ou modifiez les objets existants), vous verrez l'attribut nouvellement ajouté sur l'instance.
Cependant, si nous modifions complètement la propriété Prototype de la fonction (en attribuant 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é du constructeur:
La copie de code est la suivante:
fonction a () {}
A.prototype = {
X: 10
};
var a = new a ();
alerte (AX); // 10
alerte (A. Constructor === A); // FAUX!
Par conséquent, les références prototypes aux fonctions doivent être restaurées manuellement:
La copie de code est la suivante:
fonction a () {}
A.prototype = {
Constructeur: A,
X: 10
};
var a = new a ();
alerte (AX); // 10
alerte (A. Constructor === A); // vrai
Notez que bien que l'attribut de constructeur soit restauré manuellement, par rapport au prototype manquant d'origine, la fonction {dontenum} est partie, c'est-à-dire que l'instruction FOR..in dans A.Prototype n'est plus prise en charge, mais dans la 5ème édition, la fonction [[énumérable]] offre la capacité.
La copie de code est la suivante:
var foo = {x: 10};
Object.defineproperty (foo, "y", {
Valeur: 20,
Énumérable: false // aka {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.Enunuable, // Vrai
YDESC.Enunuable // Faux
));
Prototype explicite et propriétés implicites [[prototype]]
Généralement, il est incorrect de se référer explicitement au prototype d'un objet via la propriété Prototype de la fonction. Il fait référence au même objet, la propriété [[prototype]] de l'objet:
a. [[Prototype]] ---> Prototype <---- a.prototype
De plus, la valeur [[prototype]] de l'instance est en effet obtenue sur la propriété prototype du constructeur.
Cependant, la soumission de l'attribut prototype n'affectera pas le prototype qui a été créé (il affectera uniquement que l'attribut prototype du constructeur change), c'est-à-dire que l'objet nouvellement créé a un nouveau prototype, et l'objet créé se réfère toujours à l'ancien prototype d'origine (ce prototype ne peut plus être modifié).
La copie de code est la suivante:
// la situation avant de modifier le prototype A.Prototype
a. [[Prototype]] ---> Prototype <---- a.prototype
// après modification
A.prototype ---> nouveau prototype // nouvel objet aura ce prototype
a. [[Prototype]] ---> Prototype // sur le prototype d'origine du démarrage
Par exemple:
La copie de code est la suivante:
fonction a () {}
A.prototype.x = 10;
var a = new a ();
alerte (AX); // 10
A.prototype = {
Constructeur: A,
X: 20
Y: 30
};
// L'objet A est une valeur obtenue à partir du prototype du pétrole brut via une référence implicite [[prototype]]
alerte (AX); // 10
alerte (ay) // non défini
var b = new a ();
// mais le nouvel objet est la valeur obtenue du nouveau prototype
alerte (bx); // 20
alerte (par) // 30
Par conséquent, il est faux pour certains articles de dire que "la modification dynamique des prototypes affectera tous les objets qui ont de nouveaux prototypes", et le nouveau prototype ne prend effet que sur les objets nouvellement créés après la modification du prototype.
La règle principale ici est: le prototype de l'objet est créé lorsque l'objet est créé et ne peut pas être modifié en un nouvel objet après cela. Si le même objet est toujours référencé, il peut être référencé via le prototype explicite du constructeur. Une fois l'objet créé, les propriétés du prototype ne peuvent être ajoutées ou modifiées.
Attributs __proto__ non standard
Cependant, certaines implémentations (telles que SpiderMonkey) fournissent des propriétés explicites __proto__ non standard pour référencer le prototype de l'objet:
La copie de code est la suivante:
fonction a () {}
A.prototype.x = 10;
var a = new a ();
alerte (AX); // 10
var __newprototype = {
Constructeur: A,
X: 20,
Y: 30
};
// référence à un nouvel objet
A.prototype = __NewPrototype;
var b = new a ();
alerte (bx); // 20
alerte (by); // 30
// "a"对象使用的依然是旧的原型
alert(ax); // 10
alert(ay); // indéfini
// 显式修改原型
a.__proto__ = __newPrototype;
// 现在"а"对象引用的是新对象
alert(ax); // 20
alert(ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
La copie de code est la suivante:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // vrai
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
La copie de code est la suivante:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
La copie de code est la suivante:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
La copie de code est la suivante:
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
alert(a instanceof A); // vrai
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
La copie de code est la suivante:
function B() {}
var b = new B();
alert(b instanceof B); // vrai
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // vrai
alert(b instanceof B); // FAUX
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
La copie de code est la suivante:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// 初始化上下文
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
retour {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // vrai
alert(a.method2 === b.method2); // vrai
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
La copie de code est la suivante:
// Écrire
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
La copie de code est la suivante:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
La copie de code est la suivante:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
La copie de code est la suivante:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
La copie de code est la suivante:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
retour;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
retour;
Par exemple:
La copie de code est la suivante:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Mettre]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* rien */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
La copie de code est la suivante:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Par exemple:
La copie de code est la suivante:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // indéfini
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
La copie de code est la suivante:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
La copie de code est la suivante:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
La copie de code est la suivante:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // indéfini
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
hériter
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
La copie de code est la suivante:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
La copie de code est la suivante:
1.toString(); // 语法错误!
(1).toString(); // D'ACCORD
1..toString(); // D'ACCORD
1['toString'](); // D'ACCORD
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
La copie de code est la suivante:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new A();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
La copie de code est la suivante:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Erreur
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
La copie de code est la suivante:
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (集成)
function B() {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Citation
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
La copie de code est la suivante:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
}
因此,继承:
La copie de code est la suivante:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = new B();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
La copie de code est la suivante:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
La copie de code est la suivante:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = new B();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
La copie de code est la suivante:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
return child;
}
// 用法
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
en conclusion
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。