Maison > Article > interface Web > Parlons des compétences prototypiques javascript d'héritage_javascript
Dans le vrai sens du terme, Javascript n'est pas un langage orienté objet et ne fournit pas de méthode d'héritage traditionnelle. Cependant, il fournit une méthode d'héritage prototype, utilisant les propriétés prototypes fournies par lui-même pour réaliser l'héritage.
Prototypes et chaînes de prototypes
Avant de parler d'héritage prototypique, nous devons encore parler de prototypes et de chaînes de prototypes. Après tout, c'est la base pour réaliser l'héritage prototypique.
En Javascript, chaque fonction a un attribut prototype pointant vers son propre prototype, et l'objet créé par cette fonction a également un attribut __proto__ pointant vers ce prototype, et le prototype de la fonction est un objet, donc cet objet aura également un __proto__ pointe vers son propre prototype et va plus profondément couche par couche jusqu'à atteindre le prototype de l'objet Object, formant ainsi une chaîne de prototypes. L'image ci-dessous explique très bien la relation entre le prototype et la chaîne de prototypes en Javascript.
Chaque fonction est un objet créé par la fonction Function, donc chaque fonction possède également un attribut __proto__ pointant vers le prototype de la fonction Function. Ce qu'il faut souligner ici, c'est que c'est l'attribut __proto__ de chaque objet qui forme réellement la chaîne de prototypes, et non l'attribut prototype de la fonction, ce qui est très important.
Héritage prototype
Mode de base
var Enfant = fonction(){
This.name = 'enfant' ;
} ;
Enfant.prototype = new Parent() ;
var parent = new Parent() ;
var enfant = new Enfant() ;
console.log(parent.getName()) ; //parent
console.log(enfant.getName()); //enfant
C'est le moyen le plus simple d'implémenter l'héritage prototypique. Attribuez directement l'objet de la classe parent au prototype du constructeur de la sous-classe, afin que l'objet de la sous-classe puisse accéder aux attributs du prototype de la classe parent et le constructeur de la classe parent. Le schéma d'héritage prototype de cette méthode est le suivant :
Les avantages de cette méthode sont évidents. L'implémentation est très simple et ne nécessite aucune opération particulière, en même temps, les inconvénients sont également évidents si la sous-classe doit effectuer la même action d'initialisation que dans la classe parent. constructeur, alors il faut le faire dans la sous-classe. Dans le constructeur, répétez l'opération dans la classe parent :
var Enfant = fonction(nom){
This.name = nom || 'enfant' ;
} ;
Enfant.prototype = new Parent() ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(parent.getName()) ; //monParent
console.log(child.getName()); //monEnfant
Dans le cas ci-dessus, seul l'attribut name doit être initialisé. Si le travail d'initialisation continue d'augmenter, cette méthode est très peu pratique. Il existe donc une méthode améliorée comme suit.
Emprunter un constructeur
var Enfant = fonction(nom){
Parent.apply(this,arguments) ;
} ;
Enfant.prototype = new Parent() ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(parent.getName()) ; //monParent
console.log(child.getName()); //monEnfant
La méthode ci-dessus effectue le même travail d'initialisation en appelant le constructeur de la classe parent via apply dans le constructeur de la sous-classe. De cette façon, quelle que soit la quantité de travail d'initialisation effectuée dans la classe parent, la sous-classe peut également effectuer la même chose. travail d'initialisation. Mais il y a toujours un problème avec l'implémentation ci-dessus. Le constructeur de la classe parent est exécuté deux fois, une fois dans le constructeur de la sous-classe et une fois lors de l'attribution du prototype de la sous-classe, c'est très redondant, nous devons donc encore apporter une amélioration :
.var Enfant = fonction(nom){
Parent.apply(this,arguments) ;
} ;
Enfant.prototype = Parent.prototype ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(parent.getName()) ; //monParent
console.log(child.getName()); //monEnfant
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 la classe enfant, et en même temps, nous pouvons hériter des attributs du prototype de la classe parent. Ceci est également plus conforme à l'intention initiale. du prototype, qui consiste à mettre le contenu qui doit être réutilisé. Dans le prototype, nous héritons uniquement du contenu réutilisable dans le prototype. Le prototype de la méthode ci-dessus est le suivant :
Modèle de constructeur temporaire (modèle du Saint Graal)
La dernière version améliorée qui emprunte le modèle de constructeur ci-dessus a encore des problèmes. Elle assigne directement le prototype de la classe parent au prototype de la sous-classe, c'est-à-dire si le prototype de la sous-classe est modifié. , alors ces modifications affecteront également le prototype de la classe parent, et donc l'objet de la classe parent. Ce n'est certainement pas ce que tout le monde veut voir. Pour résoudre ce problème, il existe le modèle de constructeur temporaire.
var Enfant = fonction(nom){
Parent.apply(this,arguments) ;
} ;
var F = nouvelle fonction(){};
F.prototype = Parent.prototype ;
Enfant.prototype = new F() ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(parent.getName()) ; //monParent
console.log(child.getName()); //monEnfant
Le diagramme d'héritage prototype de cette méthode est le suivant :
Il est facile de voir qu'en ajoutant un constructeur temporaire F entre le prototype de la classe parent et le prototype de la sous-classe, la connexion entre le prototype de la sous-classe et le prototype de la classe parent est coupée, de sorte que lorsque le prototype de la sous-classe est modifié, ce sera n'affecte pas le prototype de la classe parent.
Ma méthode
Le mode Saint Graal est terminé en "Mode Javascript", mais quelle que soit la méthode ci-dessus, il y a un problème qui n'est pas facile à trouver. Vous pouvez voir que j'ai ajouté un attribut littéral d'objet obj à l'attribut prototype de 'Parent', mais il n'a jamais été utilisé. Jetons un coup d'œil à la situation suivante basée sur le modèle du Saint Graal :
var Enfant = fonction(nom){
Parent.apply(this,arguments) ;
} ;
var F = nouvelle fonction(){};
F.prototype = Parent.prototype ;
Enfant.prototype = new F() ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(enfant.obj.a) ; //1
console.log(parent.obj.a); //1
enfant.obj.a = 2;
console.log(enfant.obj.a); //2
console.log(parent.obj.a); //2
Dans la situation ci-dessus, lorsque je modifie l'objet enfant obj.a, obj.a dans le prototype de la classe parent sera également modifié, ce qui pose le même problème que le prototype partagé. Cette situation se produit car lors de l'accès à child.obj.a, nous suivrons la chaîne de prototypes pour trouver le prototype de la classe parent, puis trouverons l'attribut obj, puis modifierons obj.a. Jetons un coup d'œil à la situation suivante :
var Enfant = fonction(nom){
Parent.apply(this,arguments) ;
} ;
var F = nouvelle fonction(){};
F.prototype = Parent.prototype ;
Enfant.prototype = new F() ;
var parent = new Parent('myParent') ;
var enfant = new Child('myChild') ;
console.log(enfant.obj.a) ; //1
console.log(parent.obj.a); //1
enfant.obj.a = 2;
console.log(enfant.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 pour l'objet, c'est-à-dire que l'objet enfant peut lire l'objet obj, mais ne peut pas le modifier. l'objet obj dans le prototype.Référence, donc lorsque l'enfant modifie obj, cela n'affectera pas l'obj dans le prototype. Il ajoute simplement un attribut obj à son propre objet, écrasant l'attribut obj dans le prototype de la classe parent. Lorsque l'objet enfant modifie obj.a, il lit d'abord la référence à obj dans le prototype. À ce stade, child.obj et Parent.prototype.obj pointent vers le même objet, donc la modification d'obj.a par l'enfant le fera. affecter La valeur de Parent.prototype.obj.a, qui à son tour affecte l'objet de la classe parent. La méthode d'héritage d'imbrication $scope dans AngularJS est implémentée par héritage prototypique en Javascript.
Selon la description ci-dessus, tant que le prototype accessible dans l'objet de sous-classe est le même objet que le prototype de la classe parent, alors la situation ci-dessus se produira, nous pouvons donc copier le prototype de la classe parent puis l'attribuer au prototype de la sous-classe, Ainsi, lorsqu'une sous-classe modifie les propriétés du prototype, elle modifie uniquement une copie du prototype de la classe parent et n'affecte pas le prototype de la classe parent. La mise en œuvre spécifique est la suivante :
Parent.apply(this,arguments) ;
} ;
Enfant.prototype = deepClone(Parent.prototype) ;
var parent = new Parent('parent') ;
console.log(parent.obj.a); //1
enfant.obj.a = '2' ;
console.log(enfant.obj.a); //2
console.log(parent.obj.a); //1
var extend = fonction (Parent, Enfant){
Enfant = Enfant || function(){} ;
if(Parent === non défini)
retourner Enfant ;
//借用父类构造函数
Enfant = fonction(){
Parent.apply(this,argument) ;
} ;
//通过深拷贝继承父类原型
Enfant.prototype = deepClone(Parent.prototype) ;
//重置constructeur属性
Enfant.prototype.constructor = Enfant ;
} ;
总结
说了这么多,其实Javascript中实现继承是十分灵活多样的,并没有一种最好的方法,需要根据不同的需求实现不同方式的继承,最重要的是要理解Javascript中实现继承的原理,也就是原型和原型链的问题,只要理解了这些,自己实现继承就可以游刃有余。