explainHeritage
// First example: appending a chain to a prototype
function Mammal () {
this.isMammal = "yes";
}
function MammalSpecies (sMammalSpecies) {
this.species = sMammalSpecies;
}
MammalSpecies.prototype = new Mammal();
MammalSpecies.prototype.constructor = MammalSpecies;
var oCat = new MammalSpecies("Felis");
alert(oCat.isMammal); // "yes"
function Animal () {
this.breathing = "yes";
}
Object.appendChain(oCat, new Animal());
alert(oCat.breathing); // "yes"
// Second example: transforming a primitive value into an instance of its constructor and append its chain to a prototype
function Symbol () {
this.isSymbol = "yes";
}
var nPrime = 17;
alert(typeof nPrime); // "number"
var oPrime = Object.appendChain(nPrime, new Symbol());
alert(oPrime); // "17"
alert(oPrime.isSymbol); // "yes"
alert(typeof oPrime); // "object"
// Third example: appending a chain to the Function.prototype object and appending a new function to that chain
function Person (sName) {
this.identity = sName;
}
var george = Object.appendChain(new Person("George"), "alert(\"Hello guys!!\");");
alert(george.identity); // "George"
george(); // "Hello guys!!"
Function
)Il ne faut pas confondre "hériter d'un objet natif JavaScript" et "étendre un objet natif" JavaScript". Cet article explique la différence entre ces deux notions.
Attention, extends
est un mot réservé du langage.
L'idée est bonne et largement employée (CoffeeScript comme mentionné) mais cela ne s'applique pas "aux classes innées (built-in)" du langage. C'est bien pour étendre les "classes customs" qui se basent sur le constructeur de function
mais pas pour les éléments natifs (Array, Date, etc). On ne peut pas, ou presque, les étendre.
Avec ton code, on est censé appeler toutes les méthodes de la classe mère (ici Date), non ? Alors pourquoi :
Date.UTC(2012,02,30); // ok
MyDate.UTC(2012,02,30); // has no method 'UTC'
ou encore :
var d = new MyDate();
console.log(d.getHours()); // this is not a Date object.
Car, en dur dans le moteur Javascript, chaque méthode de Date va vérifier que l'objet en question est bien une instance de Date. Voici un extrait du code V8 (pour prendre un moteur en exemple) :
function DateGetHours() {
var t = DATE_VALUE(this);
if (NUMBER_IS_NAN(t)) return t;
return HOUR_FROM_TIME(LocalTimeNoCheck(t));
}
où DATE_VALUE
est une macro : DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError());
, qui check donc si l'objet passé est bien une instance de Date
.
Vérification : (ça affiche la propriété interne [[Class]]
qui permet de catégoriser les objets JS en fonction de leur instanciation)
Object.prototype.toString.call(new Date()); // Date
Object.prototype.toString.call(new MyDate()); // Object
Donc, si une date n'est pas instanciée en utilisant new Date()
, l'objet ne sera pas une Date et ne pourra pas être manipulé comme tel. Contrairement aux autres "wrappers" JS, Date n'a pas de syntaxe littéral (new Object
=> {}
, new Array
=> []
, new Number
=> 1
, new String()
=> ""
, etc)
Ceci est valable pour tous les autres objets natifs de JavaScript. (tel que Array
par exemple).
Il faut faire la différence entre :
prototype
(A.prototype
)Mais on choisit souvent la deuxième option car la première est quasiment impossible (comme on vient de le voir pour Date
) à cause des instances qui ont :
[[Class]]
, ou encore [[PrimitiveValue]]
, etc ... qui ne sont pas accessibles directement en Javascriptsuper
).Par exemple, quand une instance de Array
est créée (exemple : var tableau = [1, 2, 3]
), tableau
est accompagné d'une méthode interne (manipulée uniquement par le moteur JS) : [[DefineOwnProperty]]
. Cette méthode "écoute" en continue ce qui se passe sur notre tableau
. Par exemple, quand on lui ajoute un élément (tableau.push(4);
), cette méthode interne se charge d'incrémenter length
. C'est transparent pour nous développeur.
C'est une des raisons pourquoi il est difficile d'étendre ces types natifs de Javascript, à cause de ces fonctions et propriétés invisibles qui ne sont pas "recopiés" lors de l'appel du constructeur.
Cet article présente les différentes manières de faire de l'héritage en JavaScript. Il répondra aussi à ces questions :
[img center] La POO classique, c'est pas automatique.
Un des plus gros problèmes, voire LE plus gros problème, de JavaScript est qu'il n'est pas appris au même titre que l'est Java ou encore C++ (pour ne citer qu'eux). Résultat ? Les développeurs ont, souvent, la notion du paradigme POO orientée classes (POO classique), et pensent, pour beaucoup d'entre eux, que c'est la seule façon de faire. C'est faux. La POO ne se limite pas qu'à ce paradigme.
JavaScript est conçu pour faire de la POO orientée prototype.
La faute aux nombreux codes qui trainent sur le net et que les développeurs non avertis prennent un malin plaisir à réutiliser. Je pense par exemple à celles-ci :
Oui, les deux pattern "passent par prototype". Mais il y a quand même une nuance énorme. L'un est connu comme le pattern constructor de l'héritage par prototype. L'autre est le pattern prototype de l'héritage par prototype.
Le mot clé new
était très en vogue et a été amené dans JavaScript. D'ailleurs, ceci n'a pas été influencé par Java 2. L'erreur, qui apporte beaucoup de confusion, a été d'utiliser les function
comme constructeur d'objet en plus de l'utilisation "classique" qui est de faire une suite de traitement et de renvoyer un résultat.
Je pense que tu confonds la propriété prototype
(attachée d'office aux objets créés via new Function
ou par sa forme littérale function [toto](){}
), du prototype de chaque objet (instance) qui permet de lier un objet "fils" à un objet "père".
####(ou le pattern constructor de l'héritage par prototype) Egalement appelé en anglais : Delegation / Differential Inheritance, Pseudo classical Inheritance
Il emploie le mot clé new
suivi de Truc()
(qui est un objet Function
). Cet objet contient donc par défaut la propriété prototype
qui va contenir toutes les méthodes/attributs "mères" de l'instance que l'on veut créer. Truc
contient également par défaut la propriété prototype.constructor
qui contient les directives à réaliser à l'emploie de new
.
Truc
a également un prototype (comprendre un père), comme tout objet. En l'occurrence, le prototype de Truc
est Function
function Truc() {
/* Ceci sera contenu dans la propriété Truc.prototype.constructor */
this.nom = 'Eich';
this.prenom = 'Brendan';
}
/* Ceci sera contenu dans la propriété Truc.prototype */
Truc.prototype.method1 = function () {
console.log('Je suis la méthode 1');
};
Truc.prototype.method2 = function () {
console.log('Je suis la méthode 2');
};
/* Tu préféreras d'ailleurs cette forme équivalente histoire de mutualiser un peu */
Truc.prototype = {
method1: function () {
console.log('Je suis la méthode 1');
},
method2: function () {
console.log('Je suis la méthode 2');
}
}
/* Je créée une instance truc1, créée à partir de Truc */
var truc1 = new Truc();
Si on décortique truc1 :
Il contient deux propriétés par défaut, telles définies dans le constructor
:
prototype
de Truc
:Là on cela est dangereux, c'est si tu oublies new
. En l'oubliant, prototype.constructor
ne sera pas appelé et this
ne "pointera" pas sur ton instance mais sur l'objet global window
.
/* A NE PAS FAIRE */
var truc1 = Truc();
Ici, tu appelles une fonction en tant que telle, et non plus un constructeur d'instance. Une fonction, par définition (sans être appelée par new
), retourne toujours une valeur. Si une valeur n'est pas explicitement retournée (à l'aide de return
), undefined
est retournée. C'est ce qui se passe dans ce cas, undefined
est retourné et stocké dans truc1
. Quant à Truc()
, il place nom
et prenom
dans this
(window
).
C'est donc le gros danger : confondre les fonctions "classiques" des fonctions faisant office de constructeur.
Citation Crockford : I have been writing JavaScript for 8 years now, and I have never once found need to use an uber function. The super idea is fairly important in the classical pattern, but it appears to be unnecessary in the prototypal and functional patterns. I now see my early attempts to support the classical model in JavaScript as a mistake.
Comment en est-on arrivé à avoir peur des prototypes alors qu'ils sont un des piliers fondateurs du langage ?
Il utilise Object.create
. Il n'y a plus de notion de new
ou de this
, et là est toute la différence. On dit aussi que c'est de l'héritage par délégation. Je te renvoie sur mon lien ci dessus pour des exemples. On ne manipule que des objets.
/* Prototype Animal */
var Animal = {
init: function(nom, caracsPerso, niveau) {
this.nom = nom || 'Inconnu'; // si nom n'est pas renseigné : 'Inconnu' par défaut
this.niveau = niveau || 1; // si niveau n'est pas renseigné : 1 par défaut
this.caracs = caracsPerso || this.caracDefault;
},
caracDefault: {attaque: 0, defense: 0},
setAttaque: function(attaque) {
/* code pour changer la carac */
},
setDefense: function(defense) {
/* code pour changer la carac */
},
toString: function() {
return('['+ this.nom + ' Animal]');
}
};
/* Prototype Ours */
var Ours = Object.create(Animal);
Ours.toString = function() {
return('['+ this.nom + ' Ours]');
}
Ours.caracDefault = {attaque: 0, defense: 1};
/* Prototype Loup */
var Loup = Object.create(Animal);
Loup.toString = function() {
return('['+ this.nom + ' Loup]');
}
Loup.caracDefault = {attaque: 1, defense: 0};
/* Création des instances */
ours1 = Object.create(Ours);
ours1.init('Baloo');
loup1 = Object.create(Loup);
loup1.init('Croc blanc', {attaque: 5, defense: 5});
loup2 = Object.create(Loup);
loup2.init('Ptit loup');
console.log(ours1);
console.log(loup1);
console.log(loup2);
Avec ce pattern, il faut systématiquement appeler init
pour chaque instance.
Tout ce qui est dans init
sera copié dans l'instance car ce sont des caractéristiques propres à chaque instance d'animal.
Chaque instance pointe vers un __proto__
(Parent) (Loup ou Ours)
Loup et Ours pointent eux même vers un __proto__
(Animal).
Animal, enfin, pointe sur le __proto__
de base JavaScript : Object
C'est ce qu'on appelle la chaîne de prototype.
Si tu demandes une propriété, JavaScript va regarder dans l'objet (ours1) s'il la trouve. S'il ne la trouve pas, il va checker dans le proto, puis le proto suivant, etc, jusqu'à la fin. S'il ne trouve rien, il renvoie undefined
.
Ce qui donne, pour mon instance ours1 : ours1 > Ours > Animal > Object
Imagine que tu veuilles la propriété caracDefault
.
JS check dans ours1 : pas trouvé.
JS check dans le proto Ours : on renvoie car c'est présent.
(sinon, il aurait renvoyé celle d'Animal)
En terme de mémoire, tu auras donc un seul objet Ours, un seul objet Loup, un seul objet Animal. Et autant d'instance que tu souhaites qui pointeront vers les objets cités ci avant
à voir : http://www.developpez.net/forums/d1392956-2/webmasters-developpement-web/javascript/optimisation-poo-securite-js/#post7578190 http://www.developpez.net/forums/d1425059/webmasters-developpement-web/javascript/usage-prototypes/#post7741096