Je sais que cette réponse a 3 ans de retard mais je pense vraiment que les réponses actuelles ne fournissent pas suffisamment d'informations sur la façon dont l'héritage prototypique est meilleur que l'héritage classique .
Voyons d'abord les arguments les plus courants des programmeurs JavaScript pour défendre l'héritage prototypique (je prends ces arguments du pool de réponses actuel):
- C'est simple.
- C'est puissant.
- Cela conduit à un code plus petit et moins redondant.
- C'est dynamique et donc c'est mieux pour les langages dynamiques.
Maintenant, ces arguments sont tous valables, mais personne n'a pris la peine d'expliquer pourquoi. C'est comme dire à un enfant qu'il est important d'étudier les mathématiques. Bien sûr que oui, mais l'enfant ne s'en soucie certainement pas; et vous ne pouvez pas faire un enfant comme Maths en disant que c'est important.
Je pense que le problème de l'héritage prototypique est qu'il est expliqué du point de vue de JavaScript. J'adore JavaScript, mais l'héritage prototypique en JavaScript est faux. Contrairement à l'héritage classique, il existe deux modèles d'héritage prototypique:
- Le modèle prototypique de l'héritage prototypique.
- Le modèle constructeur de l'héritage prototypique.
Malheureusement, JavaScript utilise le modèle constructeur de l'héritage prototypique. En effet, lorsque JavaScript a été créé, Brendan Eich (le créateur de JS) voulait qu'il ressemble à Java (qui a un héritage classique):
Et nous le poussions en tant que petit frère à Java, car un langage complémentaire comme Visual Basic était à C ++ dans les familles de langages de Microsoft à l'époque.
C'est mauvais parce que lorsque les gens utilisent des constructeurs en JavaScript, ils pensent aux constructeurs héritant d'autres constructeurs. C'est faux. Dans l'héritage prototypique, les objets héritent d'autres objets. Les constructeurs n'entrent jamais en scène. C'est ce qui déroute la plupart des gens.
Les gens de langages comme Java, qui a un héritage classique, deviennent encore plus confus parce que bien que les constructeurs ressemblent à des classes, ils ne se comportent pas comme des classes. Comme l'a déclaré Douglas Crockford :
Cette indirection visait à rendre le langage plus familier aux programmeurs de formation classique, mais n'a pas réussi à le faire, comme nous pouvons le voir d'après la très faible opinion que les programmeurs Java ont de JavaScript. Le modèle de constructeur de JavaScript n'a pas séduit la foule classique. Il a également obscurci la véritable nature prototypique de JavaScript. En conséquence, il y a très peu de programmeurs qui savent utiliser efficacement le langage.
Voilà. Directement de la bouche du cheval.
Véritable héritage prototypique
L'héritage prototypique concerne les objets. Les objets héritent des propriétés des autres objets. C'est tout ce qu'on peut en dire. Il existe deux façons de créer des objets à l'aide de l'héritage prototypique:
- Créez un tout nouvel objet.
- Clonez un objet existant et étendez-le.
Remarque: JavaScript propose deux façons de cloner un objet: la délégation et la concaténation . Dorénavant, j'utiliserai le mot "clone" pour désigner exclusivement l'héritage via délégation, et le mot "copie" pour désigner exclusivement l'héritage via concaténation.
Assez parlé. Voyons quelques exemples. Disons que j'ai un cercle de rayon 5
:
var circle = {
radius: 5
};
Nous pouvons calculer l'aire et la circonférence du cercle à partir de son rayon:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
Maintenant, je veux créer un autre cercle de rayon 10
. Une façon de procéder serait:
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
Cependant, JavaScript offre un meilleur moyen - la délégation . La Object.create
fonction est utilisée pour ce faire:
var circle2 = Object.create(circle);
circle2.radius = 10;
C'est tout. Vous venez de faire l'héritage prototypique en JavaScript. N'était-ce pas aussi simple? Vous prenez un objet, le clonez, changez tout ce dont vous avez besoin, et hé hop - vous vous êtes procuré un tout nouvel objet.
Maintenant, vous pourriez demander: "Comment est-ce simple? Chaque fois que je veux créer un nouveau cercle, je dois cloner circle
et lui attribuer manuellement un rayon". Eh bien la solution est d'utiliser une fonction pour faire le gros du travail pour vous:
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
En fait, vous pouvez combiner tout cela en un seul objet littéral comme suit:
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
Héritage prototypique en JavaScript
Si vous remarquez dans le programme ci-dessus que la create
fonction crée un clone de circle
, lui attribue un nouveau radius
et le renvoie ensuite. C'est exactement ce que fait un constructeur en JavaScript:
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
Le modèle constructeur en JavaScript est le modèle prototype inversé. Au lieu de créer un objet, vous créez un constructeur. Le new
mot-clé lie le this
pointeur à l'intérieur du constructeur à un clone de celui prototype
du constructeur.
Cela vous semble confus? C'est parce que le modèle de constructeur en JavaScript complique inutilement les choses. C'est ce que la plupart des programmeurs trouvent difficile à comprendre.
Au lieu de penser à des objets héritant d'autres objets, ils pensent aux constructeurs héritant d'autres constructeurs et deviennent alors complètement confus.
Il y a tout un tas d'autres raisons pour lesquelles le modèle de constructeur en JavaScript doit être évité. Vous pouvez en lire plus dans mon article de blog ici: Constructeurs vs prototypes
Quels sont donc les avantages de l'héritage prototypique par rapport à l'héritage classique? Reprenons les arguments les plus courants et expliquons pourquoi .
1. L'héritage prototypique est simple
CMS déclare dans sa réponse:
À mon avis, le principal avantage de l'héritage prototypique est sa simplicité.
Voyons ce que nous venons de faire. Nous avons créé un objet circle
qui avait un rayon de 5
. Ensuite, nous l'avons cloné et avons donné au clone un rayon de 10
.
Par conséquent, nous n'avons besoin que de deux choses pour faire fonctionner l'héritage prototypique:
- Un moyen de créer un nouvel objet (par exemple des littéraux d'objet).
- Une façon d'étendre un objet existant (par exemple
Object.create
).
En revanche, l'héritage classique est beaucoup plus compliqué. En héritage classique, vous avez:
- Des classes.
- Objet.
- Interfaces.
- Classes abstraites.
- Classes finales.
- Classes de base virtuelles.
- Constructeurs.
- Destructeurs.
Vous avez eu l'idée. Le fait est que l'héritage prototypique est plus facile à comprendre, à mettre en œuvre et à raisonner.
Comme Steve Yegge le dit dans son blog classique " Portrait of a N00b ":
Les métadonnées sont tout type de description ou de modèle d'autre chose. Les commentaires dans votre code ne sont qu'une description en langage naturel du calcul. Ce qui fait des métadonnées de métadonnées, c'est qu'elles ne sont pas strictement nécessaires. Si j'ai un chien avec des documents généalogiques et que je perds les documents, j'ai toujours un chien parfaitement valide.
Dans le même sens, les classes ne sont que des métadonnées. Les classes ne sont pas strictement requises pour l'héritage. Cependant, certaines personnes (généralement n00bs) trouvent les classes plus confortables avec lesquelles travailler. Cela leur donne un faux sentiment de sécurité.
Eh bien, nous savons également que les types statiques ne sont que des métadonnées. Il s'agit d'un type de commentaire spécialisé destiné à deux types de lecteurs: les programmeurs et les compilateurs. Les types statiques racontent une histoire sur le calcul, vraisemblablement pour aider les deux groupes de lecteurs à comprendre l'intention du programme. Mais les types statiques peuvent être supprimés lors de l'exécution, car au final, ce ne sont que des commentaires stylisés. Ils sont comme de la paperasse généalogique: cela pourrait rendre un certain type de personnalité peu sûr plus heureux avec leur chien, mais le chien ne s'en soucie certainement pas.
Comme je l'ai dit plus tôt, les cours donnent aux gens un faux sentiment de sécurité. Par exemple, vous obtenez trop de NullPointerException
s en Java même lorsque votre code est parfaitement lisible. Je trouve que l'héritage classique gêne généralement la programmation, mais c'est peut-être juste Java. Python possède un incroyable système d'héritage classique.
2. L'héritage prototypique est puissant
La plupart des programmeurs issus d'un milieu classique soutiennent que l'héritage classique est plus puissant que l'héritage prototypique car il a:
- Variables privées.
- Héritage multiple.
Cette affirmation est fausse. Nous savons déjà que JavaScript prend en charge les variables privées via des fermetures , mais qu'en est-il de l'héritage multiple? Les objets en JavaScript n'ont qu'un seul prototype.
La vérité est que l'héritage prototypique prend en charge l'héritage de plusieurs prototypes. L'héritage prototypique signifie simplement un objet héritant d'un autre objet. Il existe en fait deux façons d'implémenter l'héritage prototypique :
- Délégation ou héritage différentiel
- Clonage ou héritage concaténatif
Oui, JavaScript permet uniquement aux objets de se déléguer à un autre objet. Cependant, il vous permet de copier les propriétés d'un nombre arbitraire d'objets. Par exemple, _.extend
fait juste cela.
Bien sûr, de nombreux programmeurs ne considèrent pas cela comme un véritable héritage instanceof
et isPrototypeOf
disent le contraire. Cependant, cela peut être facilement résolu en stockant un tableau de prototypes sur chaque objet qui hérite d'un prototype via la concaténation:
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
L'héritage prototypique est donc tout aussi puissant que l'héritage classique. En fait, il est beaucoup plus puissant que l'héritage classique, car dans l'héritage prototypique, vous pouvez choisir manuellement les propriétés à copier et les propriétés à omettre des différents prototypes.
Dans l'héritage classique, il est impossible (ou du moins très difficile) de choisir les propriétés dont vous souhaitez hériter. Ils utilisent des classes de base virtuelles et des interfaces pour résoudre le problème du diamant .
En JavaScript, cependant, vous n'aurez probablement jamais entendu parler du problème du diamant, car vous pouvez contrôler exactement quelles propriétés vous souhaitez hériter et de quels prototypes.
3. L'héritage prototypique est moins redondant
Ce point est un peu plus difficile à expliquer car l'héritage classique ne conduit pas nécessairement à un code plus redondant. En fait, l'héritage, qu'il soit classique ou prototypique, est utilisé pour réduire la redondance dans le code.
Un argument pourrait être que la plupart des langages de programmation avec héritage classique sont typés statiquement et nécessitent que l'utilisateur déclare explicitement les types (contrairement à Haskell qui a un typage statique implicite). Par conséquent, cela conduit à un code plus détaillé.
Java est connu pour ce comportement. Je me souviens clairement que Bob Nystrom avait mentionné l'anecdote suivante dans son article de blog sur Pratt Parsers :
Vous devez aimer le niveau de bureaucratie de Java "veuillez le signer en quatre exemplaires" ici.
Encore une fois, je pense que c'est uniquement parce que Java est nul.
Un argument valable est que toutes les langues qui ont un héritage classique ne prennent pas en charge l'héritage multiple. Encore une fois, Java me vient à l'esprit. Oui, Java a des interfaces, mais ce n'est pas suffisant. Parfois, vous avez vraiment besoin d'un héritage multiple.
Étant donné que l'héritage prototypique permet l'héritage multiple, le code qui requiert l'héritage multiple est moins redondant s'il est écrit en utilisant l'héritage prototypique plutôt que dans un langage qui a l'héritage classique mais pas d'héritage multiple.
4. L'héritage prototypique est dynamique
L'un des avantages les plus importants de l'héritage prototypique est que vous pouvez ajouter de nouvelles propriétés aux prototypes après leur création. Cela vous permet d'ajouter de nouvelles méthodes à un prototype qui seront automatiquement mises à la disposition de tous les objets qui délègueront à ce prototype.
Cela n'est pas possible dans l'héritage classique car une fois qu'une classe est créée, vous ne pouvez pas la modifier au moment de l'exécution. C'est probablement le plus grand avantage de l'héritage prototypique par rapport à l'héritage classique, et il aurait dû être au sommet. Cependant j'aime garder le meilleur pour la fin.
Conclusion
L'héritage prototypique est important. Il est important d'éduquer les programmeurs JavaScript sur les raisons d'abandonner le modèle constructeur d'héritage prototypique au profit du modèle prototypique d'héritage prototypique.
Nous devons commencer à enseigner correctement JavaScript et cela signifie montrer aux nouveaux programmeurs comment écrire du code en utilisant le modèle prototypique au lieu du modèle constructeur.
Non seulement il sera plus facile d'expliquer l'héritage prototypique à l'aide du modèle prototypique, mais cela fera également de meilleurs programmeurs.
Si vous avez aimé cette réponse, vous devriez également lire mon article de blog sur " Pourquoi l'héritage prototypique est important ". Croyez-moi, vous ne serez pas déçu.