JavaScript est une langue orientée objet. Il y a un dicton très classique dans Javascript: tout est un objet. Puisqu'il est orienté objet, il existe trois caractéristiques majeures de l'objet: l'encapsulation, l'héritage et le polymorphisme. Ici, nous parlons de l'héritage de JavaScript, et nous parlerons des deux autres plus tard.
L'héritage de JavaScript n'est pas beaucoup le même que celui de C ++. L'héritage de C ++ est basé sur la classe, tandis que l'héritage de JavaScript est basé sur des prototypes.
Maintenant, le problème est là.
Quel est le prototype? Pour les prototypes, nous pouvons nous référer aux classes en C ++ et enregistrer les propriétés et les méthodes de l'objet. Par exemple, écrivons un objet simple
La copie de code est la suivante:
fonction animal (nom) {
this.name = name;
}
Animal.prototype.setname = fonction (nom) {
this.name = name;
}
var animal = nouvel animal ("wangwang");
Nous pouvons voir qu'il s'agit d'un animal d'objet, qui a un nom d'attribut et un nom de méthode. Notez qu'une fois le prototype modifié, comme l'ajout d'une méthode, toutes les instances de l'objet partageront cette méthode. Par exemple
La copie de code est la suivante:
fonction animal (nom) {
this.name = name;
}
var animal = nouvel animal ("wangwang");
Pour le moment, Animal n'a que l'attribut de nom. Si nous ajoutons une phrase,
La copie de code est la suivante:
Animal.prototype.setname = fonction (nom) {
this.name = name;
}
À l'heure actuelle, Animal aura également une méthode SetName.
Copie d'héritage - À partir d'un objet vide, nous savons que parmi les types de base de JS, il existe un type appelé objet, et son instance la plus élémentaire est un objet vide, c'est-à-dire l'instance générée en appelant un nouvel objet () directement, ou déclaré avec le littéral {}. Un objet vide est un "objet propre", avec uniquement des propriétés et des méthodes prédéfinies, tandis que tous les autres objets sont hérités d'objets vides, de sorte que tous les objets ont ces propriétés et méthodes prédéfinies. Le prototype est en fait une instance d'objet. La signification d'un prototype est: si le constructeur a un objet prototype A, les instances créées par le constructeur doivent être copiées à partir de A. Puisque l'instance est copiée à partir de l'objet A, l'instance doit hériter de toutes les propriétés, méthodes et autres propriétés de A. Alors, comment la réplication est-elle implémentée? Méthode 1: Copie de construction Chaque instance construite est copiée à partir du prototype, et la nouvelle instance occupe le même espace mémoire que le prototype. Bien que cela rend OBJ1 et OBJ2 "complètement cohérent" avec leurs prototypes, il est également très peu rentable - la consommation d'espace mémoire augmentera rapidement. Comme indiqué dans l'image:
Méthode 2: Copie on Write Cette stratégie provient de la technologie d'un système de déception cohérent: Copie sur l'écriture. Un exemple typique de ce type de fraude est la bibliothèque de liens dynamiques (DDL) dans le système d'exploitation, dont la zone de mémoire est toujours copiée sur l'écriture. Comme indiqué dans l'image:
Nous avons juste besoin de spécifier dans le système que OBJ1 et OBJ2 sont équivalents à leurs prototypes, donc lors de la lecture, nous n'avons qu'à suivre les instructions pour lire le prototype. Lorsque nous avons besoin d'écrire les propriétés d'un objet (comme OBJ2), nous copie une image prototype et faisons pointer des opérations ultérieures vers l'image. Comme indiqué dans l'image:
L'avantage de cette méthode est que nous n'avons pas besoin de beaucoup de frais généraux de mémoire lors de la création d'instances et d'attributs de lecture. Nous n'utilisons qu'un code pour allouer la mémoire lors de l'écriture la première fois et apportant du code et de la surcharge de mémoire. Mais il n'y aura pas de tels frais généraux depuis lors, car l'efficacité de l'accès aux images et de l'accès aux prototypes est cohérente. Cependant, pour les systèmes qui rédigent souvent des opérations, cette méthode n'est pas économique que la méthode précédente. Méthode 3: Traversion de lecture Cette méthode transforme la granularité de la copie du prototype à membre. Cette méthode est caractérisée par la copie des informations des membres dans l'image d'instance uniquement lors de l'écriture d'un membre d'une instance. Lors de l'écriture de propriétés d'objet, par exemple (obj2.value = 10), une valeur d'attribut nommée sera générée et placée dans la liste des membres de l'objet OBJ2. Regardez l'image:
On peut constater que l'OBJ2 est toujours une référence au prototype, et aucune instance d'objet de la même taille que le prototype n'a été créée pendant l'opération. De cette façon, les opérations d'écriture ne conduisent pas à une grande quantité d'allocation de mémoire, donc l'utilisation de la mémoire semble économique. La différence est que l'OBJ2 (et toutes les instances d'objet) doivent maintenir une liste de membres. Cette liste de membres suit deux règles: elle est garantie d'être consultée en premier lors de la lecture. Si aucun attribut n'est spécifié dans l'objet, essayez de traverser toute la chaîne prototype de l'objet jusqu'à ce que le prototype soit vide ou que la propriété soit trouvée. La chaîne prototype sera discutée plus tard. De toute évidence, parmi les trois méthodes, la lecture de la traversée est la meilleure performance. Par conséquent, l'héritage du prototype de JavaScript est une traversée de lecture. Les constructeurs qui connaissent C ++ seront certainement confus après avoir lu le code de l'objet supérieur. Il est facile de comprendre sans le mot clé de classe, après tout, il existe des mots clés de fonction, mais les mots clés sont différents. Mais qu'en est-il du constructeur? En fait, JavaScript a également des constructeurs similaires, mais ils sont appelés constructeurs. Lorsque vous utilisez le nouvel opérateur, le constructeur a été appelé et c'est lié comme un objet. Par exemple, nous utilisons le code suivant
La copie de code est la suivante:
var animal = animal ("wangwang");
L'animal ne sera pas défini. Certaines personnes diront qu'aucune valeur de retour n'est bien sûr non définie. Ensuite, si vous modifiez la définition de l'objet animal:
La copie de code est la suivante:
fonction animal (nom) {
this.name = name;
retourner ceci;
}
Devinez quel animal est maintenant?
Pour le moment, l'animal est devenu une fenêtre. La différence est qu'il développe la fenêtre, de sorte que la fenêtre a un attribut de nom. En effet, cela par défaut, c'est-à-dire la variable de niveau supérieur sans le spécifier. Ce n'est qu'en appelant le nouveau mot-clé que le constructeur peut être appelé correctement. Alors, comment éviter que le nouveau mot-clé soit manqué par la personne qui l'utilise? Nous pouvons apporter des modifications mineures:
La copie de code est la suivante:
fonction animal (nom) {
if (! (Cette instance d'un animal)) {
retourner nouvel animal (nom);
}
this.name = name;
}
Ce sera infaillible. Le constructeur a également une autre utilisation, indiquant quel objet appartient l'instance. Nous pouvons utiliser l'instance pour juger, mais l'instance de renverra vrai aux objets ancêtres et aux objets réels lors de l'héritage, il n'est donc pas très approprié. Lorsque le constructeur est appelé nouveau, il pointe vers l'objet actuel par défaut.
La copie de code est la suivante:
console.log (animal.prototype.constructor === animal); // vrai
Nous pouvons penser différemment: le prototype est sans valeur au début de la fonction, et l'implémentation peut être la logique suivante
// set __proto__ est un membre intégré de la fonction, get_prototyoe () est sa méthode
La copie de code est la suivante:
var __proto__ = null;
fonction get_prototype () {
if (! __ proto__) {
__proto__ = nouveau objet ();
__proto __. Constructeur = this;
}
retour __proto__;
}
Cet avantage est qu'il évite de créer une instance d'objet pour chaque fonction déclarée, enregistrant les frais généraux. Le constructeur peut être modifié, qui sera discuté plus tard. Je crois que tout le monde sait quel héritage est basé sur les prototypes, donc ils ne montrent pas leur limite de QI inférieure.
Il existe plusieurs types d'hérédité JS, voici deux types
1. Méthode 1 Cette méthode est le plus couramment utilisée et a une meilleure sécurité. Définissons d'abord deux objets
La copie de code est la suivante:
fonction animal (nom) {
this.name = name;
}
chien de fonction (âge) {
this.age = âge;
}
Var Dog = nouveau chien (2);
Il est très simple de construire l'héritage, pointer le prototype de l'objet enfant à l'instance de l'objet parent (notez qu'il s'agit d'une instance, pas d'un objet)
La copie de code est la suivante:
Dog.prototype = nouvel animal ("wangwang");
À l'heure actuelle, Dog aura deux attributs, le nom et l'âge. Et si l'instance d'opérateur est utilisée pour le chien
La copie de code est la suivante:
console.log (instance de chien de l'animal); // vrai
console.log (instance de chien de chien); // FAUX
Cela atteint l'héritage, mais il y a un petit problème
La copie de code est la suivante:
console.log (dog.prototype.constructor === animal); // vrai
console.log (dog.prototype.constructor === chien); // FAUX
Vous pouvez voir que l'objet indiqué par le constructeur a changé, ce qui ne répond pas à notre objectif. Nous ne pouvons pas déterminer à qui nous appartient l'instance. Par conséquent, nous pouvons ajouter une phrase:
La copie de code est la suivante:
Dog.prototype.constructor = chien;
Jetons un coup d'œil:
La copie de code est la suivante:
console.log (instance de chien de l'animal); // FAUX
console.log (instance de chien de chien); // vrai
fait. Cette méthode est un lien dans la maintenance de la chaîne prototype, qui sera expliquée en détail ci-dessous. 2. Méthode 2 Cette méthode a ses avantages et ses inconvénients, mais les inconvénients l'emportent sur les avantages. Regardez d'abord le code
La copie de code est la suivante:
<pren name = "code"> fonction animal (name) {
this.name = name;
}
Animal.prototype.setname = fonction (nom) {
this.name = name;
}
chien de fonction (âge) {
this.age = âge;
}
Dog.prototype = animal.prototype;
Cela permet la copie du prototype.
L'avantage de cette méthode est qu'il ne nécessite pas d'instanciation d'objets (par rapport à la méthode 1), en économie des ressources. Les inconvénients sont également évidents. En plus du même problème que ci-dessus, c'est-à-dire que le constructeur pointe vers l'objet parent, il ne peut copier que les propriétés et les méthodes déclarées par l'objet parent avec le prototype. C'est-à-dire que dans le code ci-dessus, l'attribut de nom de l'objet animal ne peut pas être copié, mais la méthode SetName peut être copiée. La chose la plus mortelle est que toute modification du prototype de l'objet enfant affectera le prototype de l'objet parent, c'est-à-dire que les instances déclarées par les deux objets seront affectées. Par conséquent, cette méthode n'est pas recommandée.
Chaîne prototype
Quiconque a écrit sur l'héritage sait que l'héritage peut être hérité de plusieurs niveaux. Et dans JS, cela forme la chaîne prototype. L'article ci-dessus a également mentionné la chaîne prototype à plusieurs reprises, alors, quelle est la chaîne prototype? Une instance doit au moins avoir un attribut Proto pointant vers le prototype, qui est la base du système d'objets en JavaScript. Cependant, cette propriété est invisible, et nous l'appelons "chaîne prototype interne" pour la distinguer de la "chaîne prototype du constructeur" composée du prototype du constructeur (c'est-à-dire ce que nous appelons habituellement "la chaîne prototype"). Construisons d'abord une relation d'héritage simple en fonction du code ci-dessus:
La copie de code est la suivante:
fonction animal (nom) {
this.name = name;
}
chien de fonction (âge) {
this.age = âge;
}
var animal = nouvel animal ("wangwang");
Dog.prototype = animal;
Var Dog = nouveau chien (2);
Comme rappel, comme mentionné précédemment, tous les objets héritent des objets vides. Nous construisons donc une chaîne prototype:
Nous pouvons voir que le prototype de l'objet enfant pointe vers l'instance de l'objet parent, formant la chaîne de prototype du constructeur. L'objet Proto intérieur de l'instance de l'enfant est également une instance pointant vers l'objet parent, formant une chaîne prototype interne. Lorsque nous devons trouver une propriété, le code est similaire à
La copie de code est la suivante:
fonction getAttrfromobj (att, obj) {
if (typeof (obj) === "objet") {
var proto = obj;
while (proto) {
if (proto.hasownproperty (attr)) {
return proto [att];
}
proto = proto .__ proto__;
}
}
retour non défini;
}
Dans cet exemple, si nous recherchons l'attribut de nom dans DOG, il sera recherché dans la liste des membres dans DOG. Bien sûr, il ne sera pas trouvé, car maintenant la liste des membres du chien n'est que l'âge de l'article. Ensuite, il continuera de rechercher le long de la chaîne prototype, c'est-à-dire que l'instance indiquée par .proto, c'est-à-dire, dans l'animal, trouver l'attribut de nom et le renvoyer. Si la recherche est une propriété qui n'existe pas, lorsqu'elle ne peut être trouvée dans Animal, elle continuera de rechercher avec .proto, trouvera un objet vide, puis continuera à rechercher avec .proto, et le .proto de l'objet vide pointe vers Null, à la recherche de sortie.
Maintenance des chaînes prototypes, nous avons soulevé une question lorsque nous avons parlé de l'héritage du prototype tout à l'heure. Lors de l'utilisation de la méthode 10 pour construire l'héritage, le constructeur de l'instance de l'objet enfant pointe vers l'objet parent. L'avantage de cela est que nous pouvons accéder à la chaîne prototype via l'attribut du constructeur, et les inconvénients sont également évidents. Un objet dont l'instance doit pointer vers elle-même, c'est-à-dire
La copie de code est la suivante:
(new obj ()). prototype.constructor === obj;
Ensuite, après avoir réécrit les propriétés du prototype, le constructeur de l'instance générée par l'objet enfant ne se dirige pas vers lui-même! Cela va à l'encontre de l'intention initiale du constructeur. Nous avons mentionné une solution ci-dessus:
La copie de code est la suivante:
Dog.prototype = nouvel animal ("wangwang");
Dog.prototype.constructor = chien;
Il semble que rien ne soit mal. Mais en fait, cela soulève un nouveau problème car nous constatons que nous ne pouvons pas revenir à la chaîne prototype parce que nous ne pouvons pas trouver l'objet parent, et l'attribut .proto de la chaîne prototype interne est inaccessible. Par conséquent, SpiderMonkey fournit une amélioration: ajoutez une propriété nommée __proto__ à tout objet créé, qui pointe toujours le prototype utilisé par le constructeur. De cette façon, toute modification du constructeur n'affectera pas la valeur de __proto__, ce qui le rend pratique pour maintenir le constructeur.
Cependant, il y a deux autres problèmes:
__proto__ est réécrit, ce qui signifie qu'il y a encore des risques lors de l'utilisation
__proto__ est un traitement spécial de SpiderMonkey et ne peut pas être utilisé dans d'autres moteurs (tels que JScript).
Il existe une autre façon pour nous de maintenir les propriétés du constructeur du prototype et d'initialiser les propriétés du constructeur de l'instance dans la fonction de constructeur de sous-classe.
Le code est le suivant: Réécrivez l'objet enfant
La copie de code est la suivante:
chien de fonction (âge) {
this.constructor = arguments.callee;
this.age = âge;
}
Dog.prototype = nouvel animal ("wangwang");
De cette façon, le constructeur de toutes les instances d'objets enfants pointe correctement l'objet, tandis que le constructeur du prototype pointe vers l'objet parent. Bien que cette méthode soit relativement inefficace car l'attribut de constructeur doit être réécrit chaque fois que l'instance est construite, il ne fait aucun doute que cette méthode peut résoudre efficacement les contradictions précédentes. ES5 en prend en compte et résout complètement ce problème: object.getPrototypeOf () peut être utilisé à tout moment pour obtenir le prototype réel d'un objet sans accéder au constructeur ou maintenir la chaîne prototype externe. Par conséquent, comme mentionné dans la section précédente, nous pouvons le réécrire comme suit:
La copie de code est la suivante:
fonction getAttrfromobj (att, obj) {
if (typeof (obj) === "objet") {
faire {
var proto = object.getPrototypeOf (chien);
if (proto [att]) {
return proto [att];
}
}
tandis que (proto);
}
retour non défini;
}
Bien sûr, cette méthode ne peut être utilisée que dans les navigateurs qui prennent en charge ES5. Afin d'être une compatibilité en arrière, nous devons toujours considérer la méthode précédente. Une méthode plus appropriée consiste à intégrer et à encapsuler ces deux méthodes. Je crois que les lecteurs sont très bons dans ce domaine, donc je ne serai pas de laideur ici.