2008-12-24 25 views
16

Je faisais de l'héritage dans js afin de mieux le comprendre, et j'ai trouvé quelque chose qui me rend confus.Javascript Prototype Héritage Doute II

Je sais que lorsque vous appelez une 'fonction constructeur' avec le nouveau mot-clé, vous obtenez un nouvel objet avec une référence au prototype de cette fonction.

Je sais aussi que pour faire l'héritage prototypique, vous devez remplacer le prototype de la fonction constructeur par une instance de l'objet que vous voulez être la 'superclasse'.

donc je l'ai fait exemple stupide d'essayer ces concepts:

function Animal(){} 
function Dog(){} 

Animal.prototype.run = function(){alert("running...")}; 

Dog.prototype = new Animal(); 
Dog.prototype.bark = function(){alert("arf!")}; 

var fido = new Dog(); 
fido.bark() //ok 
fido.run() //ok 

console.log(Dog.prototype) // its an 'Object' 
console.log(fido.prototype) // UNDEFINED 
console.log(fido.constructor.prototype == Dog.prototype) //this is true 

function KillerDog(){}; 
KillerDog.prototype.deathBite = function(){alert("AAARFFF! *bite*")} 

fido.prototype = new KillerDog(); 

console.log(fido.prototype) // no longer UNDEFINED 
fido.deathBite(); // but this doesn't work! 

(Cela a été fait dans la console de Firebug)

1) Pourquoi si tous les nouveaux objets contiennent une référence au prototype de la fonction créateur, fido.prototype est indéfini?

2) La chaîne d'héritage [obj] -> [constructeur] -> [prototype] au lieu de [obj] -> [prototype]?

3) la propriété 'prototype' de notre objet (fido) a-t-elle déjà été vérifiée? si oui ... pourquoi 'deathbite' est-il indéfini (dans la dernière partie)?

Merci!

Répondre

12

1) Pourquoi si tous les nouveaux objets contiennent une référence à prototype de la fonction créateur, fido.prototype est non définie?

Tous les nouveaux objets contiennent une référence au prototype qui était présent sur leur constructeur au moment de la construction. Cependant, le nom de la propriété utilisée pour stocker cette référence n'est pas prototype car il est sur la fonction constructeur elle-même. Certaines implémentations Javascript permettent l'accès à cette propriété 'cachée' via un nom de propriété comme __proto__ où d'autres ne le font pas (par exemple Microsofts).

2) Est-ce la chaîne d'héritage [obj] -> [constructeur] -> [prototype] au lieu de [obj] -> [prototype]?

Non. Jetez un oeil à ceci: -

function Base() {} 
Base.prototype.doThis = function() { alert("First"); } 

function Base2() {} 
Base2.prototype.doThis = function() { alert("Second"); } 

function Derived() {} 
Derived.prototype = new Base() 

var x = new Derived() 

Derived.prototype = new Base2() 

x.doThis(); 

Cette alerte "First" pas de deuxième. Si la chaîne d'héritage passait par le constructeur, nous verrions "Second". Lorsqu'un objet est construit, la référence actuelle contenue dans la propriété prototype Fonctions est transférée vers la référence cachée de l'objet à son prototype.

3) est la propriété 'prototype' de notre objet (fido) jamais vérifié? si oui ... Pourquoi 'deathBite' est-il undefined (dans la dernière partie)?

Affectation à un objet (autre qu'une fonction) une propriété appelée prototype n'a pas de signification particulière, comme indiqué précédemment un objet ne maintient pas une référence à son prototype par un tel nom de la propriété.

+1

Nice! Notez que si vous avez déclaré var x = new Derived() APRÈS le Derived.prototype = new Base2(), vous verrez 'Second'. x contient simplement un pointeur sur le Derived.prototype d'origine, mais cela ne signifie pas que nous ne l'avons pas redirigé vers Base2(). Les pointeurs ont simplement été changés. Pas du tout contredire ce qu'Anthony dit mais juste clarifier ce dernier morceau. Voir mes exemples: http://github.com/roblevintennis/Testing-and-Debugging-JavaScript/blob/master/code/objects/lib/js_inheritance.js – Rob

+0

Oh, et je pense que vous vouliez dire: __proto__ pas __prototype__ – Rob

+0

Markdown, Ergh! soulignement soulignement proto underscore soulignement – Rob

7

Vous ne pouvez pas modifier le prototype d'un objet une fois qu'il a été instancié avec new.

Dans votre exemple ci-dessus, les lignes comme

fido.prototype = new KillerDog(); 

crée simplement un nouvel attribut nommé prototype sur l'objet fido et définit cet attribut à un nouvel objet KillerDog. Il est pas différent de

fido.foo = new KillerDog(); 

Comme votre code signifie ...

// Doesn't work because objects can't be changed via their constructors 
fido.deathBite(); 

// Does work, because objects can be changed dynamically, 
// and Javascript won't complain when you use prototype 
//as an object attribute name 
fido.prototype.deathBite(); 

Le comportement prototype spécial applique uniquement aux constructeurs en javascript, où les constructeurs sont function s qui seront appelés à new.

+0

Vous pouvez dans Mozilla (Firefox etc). Mais ouais c'est habituellement impraticable. – thomasrutter

4

Réponse chiffres à vos questions:

  1. propriété prototype de l'objet n'est pas appelé prototype. La norme utilise [[prototype]] pour le désigner. Firefox rend cette propriété publique sous le nom de __proto__. La chaîne d'héritage est [obj][prototype object]. Votre hypothèse originale ([obj][constructor][prototype]) est incorrect et vous pouvez facilement réfuter en modifiant constructor et/ou constructor.prototype, et vérifier quelles méthodes peuvent être appelées sur votre [obj] — vous découvrirez que ces modifications ne changent rien.
  2. prototype La propriété sur les objets n'est pas vérifiée et n'est pas utilisée. Vous pouvez le définir comme vous le souhaitez. JavaScript l'utilise sur les objets de fonction uniquement pendant la construction de l'objet.

Pour démontrer le n ° 3 est ici le code de Dojo:

dojo.delegate = dojo._delegate = (function(){ 
    // boodman/crockford delegation w/ cornford optimization 
    function TMP(){} 
    return function(obj, props){ 
    TMP.prototype = obj; 
    var tmp = new TMP(); 
    if(props){ 
     dojo._mixin(tmp, props); 
    } 
    return tmp; // Object 
    } 
})(); 

Comme vous pouvez le voir profite du fait que prototype est utilisé que dans un seul endroit en réutilisant la même fonction TMP pour tous les objets délégués avec différents prototypes. En fait prototype est affecté directement avant d'appeler la fonction avec new, et il sera changé après que cela n'affecte aucun des objets créés.

Vous pouvez trouver la séquence créée par l'objet dans ma réponse à Relation between [[Prototype]] and prototype in JavaScript.

2

Je sais qu'on a déjà répondu, mais il y a une meilleure façon de faire de l'héritage. Appeler un constructeur juste pour l'héritage n'est pas souhaitable. L'un des effets indésirables est.

function Base() {this.a = "A"} 
function Child() {this.b = "B"}; 

Child.prototype = new Base(); 

Maintenant Vous avez ajouté la propriété "a" au prototype de l'enfant que vous n'aviez pas prévu.

Voici la bonne façon (je ne l'ai pas inventé cela, Ext-JS et d'autres libs utilisent ce)

// This is used to avoid calling a base class's constructor just to setup inheritance. 
function SurrogateCtor() {} 

/** 
* Sets a contructor to inherit from another constructor 
*/ 
function extend(BaseCtor, DerivedCtor) { 
    // Copy the prototype to the surrogate constructor 
    SurrogateCtor.prototype = BaseCtor.prototype; 
    // this sets up the inheritance chain 
    DerivedCtor.prototype = new SurrogateCtor(); 
    // Fix the constructor property, otherwise it would point to the BaseCtor 
    DerivedCtor.prototype.constructor = DerivedCtor; 
    // Might as well add a property to the constructor to 
    // allow for simpler calling of base class's method 
    DerivedCtor.superclass = BaseCtor; 
} 

function Base() { 
    this.a = "A"; 
} 

Base.prototype.getA = function() {return this.a} 

function Derived() { 
    Derived.superclass.call(this); // No need to reference the base class by name 
    this.b = "B"; 
} 

extend(Base, Derived); 
// Have to set methods on the prototype after the call to extend 
// otherwise the prototype is overridden; 
Derived.prototype.getB = function(){return this.b}; 
var obj = new Derived(); 

Une façon encore plus facile est d'ajouter un troisième paramètre à étendre où vous spécifiez la méthode de la classe dérivée de sorte que vous ne pas appeler étendre, puis ajouter des méthodes au prototype

extend(BaseCtor, DerivedCtor, { 
    getB: function() {return this.b} 
}); 

ensuite, il y a beaucoup d'autres choses que vous pouvez faire pour le sucre syntaxique.

Blogged sujet: http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html

2

A noter que dans ECMAScript 5 (la dernière version du langage JavaScript), vous pouvez accéder à la propriété interne [[Prototype]] d'une instance via Object.getPrototypeOf:

Object.getPrototypeOf(fido) === Dog.prototype