Waarom is prototypische geërfde kinderklasse een instantie van een andere kinderklasse?

Misschien doe ik het verkeerd. Ik wil een basisklasse en twee klassen instellen die overerven van die klasse. Het vertelt me ​​echter dat het ene kind een instantie is van het andere kind ... dat kan toch niet kloppen, toch? Wat doe ik hier verkeerd?

function BaseClass(){}
function Child1(){}

Child1.prototype = BaseClass.prototype;
Child1.prototype.constructor = Child1;

function Child2(){}
Child2.prototype = BaseClass.prototype;
Child2.prototype.constructor = Child2;

console.log((new Child1() instanceof Child2));//true
0
Hier is een goede bron voor de vele manieren om overerving te gebruiken in JS: 3site.eu/doc
toegevoegd de auteur Cᴏʀʏ, de bron

4 antwoord

U hebt het prototype van beide klassen ingesteld op exact hetzelfde object ("BaseClass.prototype") en stelt de eigenschap "constructor" in op "Child2". Met andere woorden: "Child1.prototype" en "Child2.prototype" zijn hetzelfde, dus er is slechts één "constructor" -eigenschap betrokken bij uw code, en het wordt uiteindelijk "Child2".

Misschien wilt u die prototypes instellen op instances van BaseClass?

Child1.prototype = new BaseClass();
Child1.prototype.constructor = Child1;

Child2.prototype = new BaseClass();//a different object
Child2.prototype.constructor = Child2;

(@Raynos wijst erop dat het instantiëren van de prototype-waarde op die manier subtiele problemen kan veroorzaken, dus u kunt de aanpak liever in zijn antwoord gebruiken.)

3
toegevoegd
Je moet echt .prototype = new X() vermijden, je wil .prototype niet initialiseren als een instantie van X, je wilt gewoon instantiëren. (d.w.z. bel de constructor niet!)
toegevoegd de auteur Raynos, de bron
@ShyGuy je initialiseert ze niet, dan doe je het in de constructor van het kind.
toegevoegd de auteur Raynos, de bron
@Pointy :( veel object-frameworks bevelen "init" aan omdat het vuile hacks zijn, je gebruikt gewoon geen constructors bij het overnemen (tip: Object.create doet wat je wilt).
toegevoegd de auteur Raynos, de bron
@Pointy Ja dat is zo, ik heb de slechte gewoonte om "ES5" te denken wanneer iemand JavaScript zegt. Ik kan het gewoon niet uitschakelen!
toegevoegd de auteur Raynos, de bron
Wat gebeurt er wanneer uw basisklasse-constructor argumenten heeft - moet u ze dan meteen initialiseren? Mijn echte kinderclass code heeft een aanroep naar BaseClass.call (dit, 'argument1', 'argument2'); in zijn constructor.
toegevoegd de auteur Paul, de bron
OK @Raynos ja dat is waarschijnlijk waar; Ik doe dat soort dingen niet vaak genoeg om met dergelijke problemen om te gaan :-) Ik zal een niet toevoegen.
toegevoegd de auteur Pointy, de bron
@ShyGuy Ik denk dat dat is waarom veel object-frameworks een conventie gebruiken om een ​​"init" -functie te gebruiken.
toegevoegd de auteur Pointy, de bron
@Raynos je weet hier zeker meer over dan ik :-) Ik heb al lang geen "object-y" -code in JavaScript geschreven, hoewel ik veel JavaScript schrijf. (Ook is Object.create geen ES5-ding? Waarschijnlijk is er een polyfill op MDN.)
toegevoegd de auteur Pointy, de bron

Child1.prototype = BaseClass.prototype;

Should be Child1.prototype = Object.create(BaseClass.prototype)

You don't want the prototype object to be the same, you want a new prototype object that inherits from BaseClass

Live-voorbeeld

Disclaimer: Object.create is ES5, use the ES5-shim for legacy platform support.

Om u een grondiger voorbeeld te geven:

// Create base prototype
var Base = {
  method: function() {
    return this.things;
  },
  constructor: function (things) {
    this.things = things;
  }
};
// Link constructor and prototype
Base.constructor.prototype = Base;

// Create child prototype that inherits from Base
var Child = Object.create(Base);
// overwrite constructor
Child.constructor = function (things) {
  things++;
  Base.constructor.call(things);
}
// Link constructor and prototype
Child.constructor.prototype = Child;

// Swap prototype and constructor around to support new
ChildFactory = Child.constructor;

var c = new ChildFactory(41);
console.log(c.method());//42

Visueel:

var Base = {...}

hier maken we eenvoudig een object met methoden en eigenschappen. We willen een exemplaar van dit object kunnen maken. Dus Base is een "klasse" en alle it's methoden en eigenschappen zijn toegankelijk vanuit de instanties (inclusief constructor ).

Base.constructor.prototype = Base;

U moet de "Constructorfunctie" koppelen, een functie die u kunt aanroepen met nieuw , waarmee u een nieuw exemplaar van het object ConstructorFunction.prototype samen met het prototype-object krijgt . Dit is eigenlijk een beetje lijm die je handmatig moet doen, omdat ES dit niet voor jou doet.

Als u dit bent vergeten, heeft de constructorfunctie (die X.constructor is) geen eigenschap .prototype om exemplaren van over te nemen.

var Child = Object.create (Base);

Maak een nieuw object waarvan [[Prototype]] Base is. Dit is in feite het begin van uw prototypeketen

Child -> ([[Prototype]] = Base) -> ([[Prototype]] = Object.prototype) -> null

Child.x = ...

Nu voegen we eigenschappen aan ons onderliggende object toe, die de methoden zijn die instanties van Kind zullen erven. In principe maken we een nieuw prototype-object waarvan we het erven. Alle instanties delen de methoden en delen ook de methoden in de prototypeketen (inclusief Base ).

ChildFactory = Child.constructor;

het nieuwe sleutelwoord werkt alleen op constructor-functies en niet op prototype-objecten, dus we moeten in principe zeggen "verander onze prototype-objectvariabele naar de constructorfunctie-variabele"

Zelf vind ik bij het opbouwen van "klassen" de prototype-objecten heel prettig om direct aan te pakken en bij het maken van instanties vind ik de constructorfuncties aangenaam om mee om te gaan.

Now when we call var c = new Child(41);

Het zal de constructorfunctie oproepen die we hebben gedefinieerd die dingen zullen verhogen, en dan de basisconstructor oproepen.

Merk op dat op dit punt de prototypeketen van c eruit ziet

c -> ([[Prototype]] = Child) 
  -> ([[Prototype]] = Base) 
  -> ([[Prototype]] = Object.prototype) 
  -> null
2
toegevoegd
Bedankt! Dit werkt als een charme ... Ik zal hier nog wat meer over moeten lezen om te zien wat er achter de schermen gebeurt. Er zijn zoveel verschillende voorbeelden op het net over hoe overerving goed te doen, en ze lijken allemaal parametere constructors te hebben om de harde stukjes te vermijden :)
toegevoegd de auteur Paul, de bron

Dit betekent niet dat u controleert of het object new Child1() is gemaakt door de constructor "Child1". De operator instanceof neemt alleen een prototype van een object - (nieuw Child1 ()). [[Prototype]] en controleert zijn aanwezigheid in de prototypeketen, met in de hoofdrol het onderzoek van de "Child1 .prototype". Operator instanceof wordt geactiveerd via de interne [[HasInstance]] -methode van de constructor. Dus je moet je code op de volgende manier wijzigen:

function BaseClass(){}
function Child1(){}

Child1.prototype = new BaseClass();
Child1.prototype.constructor = Child1;

function Child2(){}
Child2.prototype = new BaseClass();
Child2.prototype.constructor = Child2;
console.log((new Child1() instanceof Child2));//false

En het zal een correcte implementatie zijn van OOP in javascript

1
toegevoegd

Als u een prototype toewijst, betekent dat dan niet dat u het volledig overschrijft? Zodat Child1 en Child2 eigenlijk BaseClass-objecten worden, in plaats van onderliggende klassen.

Zie ook http://www.zipcon.net/~swhite /docs/computers/languages/object_oriented_JS/inheritance.html . Juiste OO in JS kost behoorlijk wat moeite.

1
toegevoegd