Dans le vrai sens, JavaScript n'est pas un langage orienté objet et ne fournit pas de méthodes d'héritage traditionnelles, mais elle fournit un moyen de l'héritage prototype, en utilisant les propriétés prototypes qu'il fournit pour atteindre l'héritage.
Prototype et chaîne prototype
Avant de parler de l'héritage des prototypes, nous devrions d'abord parler des prototypes et des chaînes de prototypes. Après tout, c'est la base pour réaliser l'héritage du prototype.
Dans JavaScript, chaque fonction a un prototype d'attribut prototype pointant vers son propre prototype, et l'objet créé par cette fonction a également un attribut __proto__ pointant vers ce prototype. Le prototype de la fonction est un objet, donc cet objet aura également un __proto__ pointant vers son propre prototype, de sorte qu'il va plus profond par calque jusqu'à ce que le prototype de l'objet objet, formant ainsi une chaîne prototype. La figure suivante explique très bien la relation entre les prototypes et les chaînes de prototypes en JavaScript.
Chaque fonction est un objet créé par la fonction de fonction, donc chaque fonction a également un attribut __proto__ pointant vers le prototype de la fonction de fonction. Il convient de souligner ici que ce qui forme vraiment la chaîne prototype, c'est l'attribut __proto__ de chaque objet, et non l'attribut prototype de la fonction, ce qui est très important.
Héritage prototype
Mode de base
La copie de code est la suivante:
var parent = function () {
this.name = 'parent';
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function () {
this.name = 'enfant';
};
Child.prototype = new Parent ();
var parent = new Parent ();
var child = new Child ();
console.log (parent.getName ()); //mère
console.log (child.getName ()); //enfant
C'est le moyen le plus simple d'implémenter l'héritage du prototype, attribuant directement l'objet de la classe parent au prototype du constructeur de sous-classe, afin que les objets de la sous-classe puissent accéder aux propriétés de la classe parent et du prototype du constructeur de la classe parent. Le prototype du diagramme d'héritage de cette méthode est le suivant:
Les avantages de cette méthode sont évidents, la mise en œuvre est très simple et ne nécessite aucune opération spéciale; Les inconvénients sont également évidents. Si la sous-classe doit effectuer les mêmes actions d'initialisation que dans le constructeur de la classe parent, vous devez répéter les opérations dans la classe parent dans le constructeur de sous-classe:
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
this.name = name || 'enfant' ;
};
Child.prototype = new Parent ();
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
Dans la situation ci-dessus, cela ne nécessite que l'initialisation de l'attribut de nom. Si le travail d'initialisation continue d'augmenter, cette méthode est très gênante. Par conséquent, il existe un moyen d'améliorer les éléments suivants.
Constructeur d'emprunteurs
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
Child.prototype = new Parent ();
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
La méthode ci-dessus effectue le même travail d'initialisation en appliquant l'appel au constructeur de classe parent dans le constructeur de sous-classe, afin que peu importe la quantité de travail d'initialisation dans la classe parent, la sous-classe peut effectuer le même travail d'initialisation. Cependant, il y a un autre problème avec la mise en œuvre ci-dessus. Le constructeur de la classe parent a été exécuté deux fois, une fois dans le constructeur de sous-classe, et une fois dans le prototype de sous-classe, c'est beaucoup de redondance, nous devons donc faire une amélioration:
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
Child.prototype = parent.prototype;
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
De cette façon, nous n'avons besoin d'exécuter le constructeur de la classe parent qu'une seule fois dans le constructeur de sous-classe, et en même temps, nous pouvons hériter des propriétés du prototype de classe parent. Ceci est plus conforme à l'intention d'origine du prototype, qui est de mettre le contenu qui doit être réutilisé dans le prototype, et nous n'héritons que le contenu réutilisable du prototype. Le diagramme prototype de la méthode ci-dessus est le suivant:
Mode du constructeur temporaire (mode Saint Graal)
Il y a toujours un problème avec la version qui a emprunté le modèle de constructeur ci-dessus. Il attribue directement le prototype de la classe parent au prototype de la sous-classe, ce qui entraînera un problème, c'est-à-dire que si le prototype de la sous-classe est modifié, la modification affectera également le prototype de la classe parent, puis affectera l'objet de classe parent. Ce n'est certainement pas ce que tout le monde espère voir. Pour résoudre ce problème, un modèle de constructeur temporaire est disponible.
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
var f = nouvelle fonction () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
Le prototype du diagramme d'héritage de cette méthode est le suivant:
Il est facile de voir qu'en ajoutant un constructeur temporaire F entre le prototype de classe parent et le prototype de classe enfant, la connexion entre le prototype de classe enfant et le prototype de classe parent est coupée, afin que le prototype de classe parent ne soit pas affecté lorsque le prototype de classe enfant est modifié.
Ma méthode
Le mode Saint Graal se termine par "mode javascript", mais quelle que soit la méthode ci-dessus, il n'y a pas de problème qui n'est pas facile à découvrir. Vous pouvez voir que j'ai ajouté un attribut littéral Object OBJ à la propriété Prototype de «Parent», mais cela n'a jamais été utile. Jetons un coup d'œil à la situation suivante en fonction du mode Saint Graal:
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
var f = nouvelle fonction () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
Dans le cas ci-dessus, lorsque je modifie l'objet enfant OBJ.A, l'OBJ.A dans le prototype de classe parent sera également modifié, ce qui entraînera le même problème que le prototype partagé. Cela se produit parce que lors de l'accès à Child.Obj.A, nous suivrons la chaîne de prototypes et trouverons le prototype de la classe parent, puis trouverons l'attribut OBJ, puis modifier OBJ.A. Jetons un coup d'œil à la situation suivante:
La copie de code est la suivante:
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: 1};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
var f = nouvelle fonction () {};
F.prototype = parent.prototype;
Child.prototype = new f ();
var parent = nouveau parent ('myparent');
var child = new Child ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
Il y a un problème clé ici. Lorsqu'un objet accède aux propriétés du prototype, les propriétés du prototype sont en lecture seule à l'objet. C'est-à-dire que l'objet enfant peut lire l'objet OBJ, mais la référence de l'objet OBJ dans le prototype ne peut pas être modifiée. Par conséquent, lorsque l'enfant modifie l'OBJ, il n'affectera pas l'OBJ dans le prototype. Il ajoute simplement un attribut OBJ à son propre objet, écrasant l'attribut OBJ dans le prototype parent. Lorsque l'objet enfant modifie OBJ.A, il lit d'abord la référence à OBJ dans le prototype. À l'heure actuelle, Child.obj et Parent.prototype.obj pointent vers le même objet, donc la modification de l'enfant de l'OBJ.A affectera la valeur de Parent.prototype.Obj.a, et affectera ainsi l'objet de la classe parent. La méthode de l'héritage de la nidification de la portée de $ dans AngularJS est mise en œuvre par modélisation de l'héritage du prototype dans Javasript.
Selon la description ci-dessus, tant que le prototype accessible dans l'objet de sous-classe est le même que le prototype de classe parent, la situation ci-dessus se produira. Par conséquent, nous pouvons copier le prototype de classe parent, puis l'attribuer au prototype de sous-classe. De cette façon, lorsque la sous-classe modifie les propriétés du prototype, il modifie uniquement une copie du prototype de classe parent et n'affectera pas le prototype de classe parent. La mise en œuvre spécifique est la suivante:
La copie de code est la suivante:
var deepclone = fonction (source, cible) {
source = source || {};
var toStr = object.prototype.tostring,
Arrtr = '[Array d'objets]';
pour (var i en source) {
if (source.hasownproperty (i)) {
var item = source [i];
if (typeof item === 'objet') {
Target [i] = (toStr.Apply (item) .tolowercase () === Arrtr): []? {};
DeepClone (élément, cible [i]);
}autre{
DeepClone (élément, cible [i]);
}
}
}
cible de retour;
};
var parent = fonction (name) {
this.name = name || «parent»;
};
Parent.prototype.getName = function () {
Renvoie ce.name;
};
Parent.prototype.obj = {a: '1'};
var child = function (name) {
Parent.Apply (ceci, arguments);
};
Child.prototype = deepClone (parent.prototype);
var child = new Child ('Child');
var parent = nouveau parent ('parent');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = '2';
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 1
Sur la base de toutes les considérations ci-dessus, l'implémentation spécifique de l'héritage JavaScript est la suivante. Uniquement lorsque l'enfant et le parent sont des fonctions sont considérés:
La copie de code est la suivante:
var deepclone = fonction (source, cible) {
source = source || {};
var toStr = object.prototype.tostring,
Arrtr = '[Array d'objets]';
pour (var i en source) {
if (source.hasownproperty (i)) {
var item = source [i];
if (typeof item === 'objet') {
Target [i] = (toStr.Apply (item) .tolowercase () === Arrtr): []? {};
DeepClone (élément, cible [i]);
}autre{
DeepClone (élément, cible [i]);
}
}
}
cible de retour;
};
var extend = fonction (parent, enfant) {
Enfant = enfant || fonction(){} ;
if (parent === Undefined)
retour de l'enfant;
// emprunter le constructeur de classe parent
Child = function () {
Parent.Apply (ce, argument);
};
// hériter du prototype de classe parent à travers une copie profonde
Child.prototype = deepClone (parent.prototype);
// Réinitialiser l'attribut du constructeur
Child.prototype.constructor = enfant;
};
Résumer
Cela dit, en fait, la mise en œuvre de l'héritage en JavaScript est très flexible et diversifiée, et il n'y a pas de meilleure méthode. Différentes méthodes d'héritage doivent être mises en œuvre en fonction des différents besoins. La chose la plus importante est de comprendre le principe de la mise en œuvre de l'héritage en JavaScript, c'est-à-dire le problème des prototypes et des chaînes de prototypes. Tant que vous les comprenez, vous pouvez facilement mettre en œuvre l'héritage par vous-même.