Gutes Beispiel für die prototypbasierte Vererbung von JavaScript


89

Ich programmiere seit über 10 Jahren mit OOP-Sprachen, lerne aber jetzt JavaScript und es ist das erste Mal, dass ich auf prototypbasierte Vererbung stoße. Ich neige dazu, am schnellsten zu lernen, indem ich guten Code studiere. Was ist ein gut geschriebenes Beispiel für eine JavaScript-Anwendung (oder Bibliothek), die die prototypische Vererbung ordnungsgemäß verwendet? Und können Sie (kurz) beschreiben, wie / wo die prototypische Vererbung verwendet wird, damit ich weiß, wo ich anfangen soll zu lesen?


1
Haben Sie die Gelegenheit bekommen, diese Basisbibliothek zu überprüfen? Es ist wirklich schön und ziemlich klein. Wenn es Ihnen gefällt, sollten Sie meine Antwort als Antwort markieren. TIA, Roland.
Roland Bouman

Ich bin wohl im selben Boot wie du. Ich möchte auch ein wenig über diese prototypische Sprache lernen und mich nicht nur auf oop-Frameworks oder ähnliches beschränken, auch wenn sie großartig sind und alles, was wir lernen müssen, richtig? Nicht nur ein Framework erledigt das für mich, auch wenn ich es verwenden werde. Aber lernen Sie, wie Sie mit neuen Methoden neue Dinge in neuen Sprachen erstellen und über den Tellerrand hinaus denken. Ich mag deine Art. Ich werde versuchen, mir zu helfen und dir vielleicht zu helfen. Sobald ich etwas finde, werde ich es dich wissen lassen.
Marcelo-Ferraz

Antworten:


48

Douglas Crockford hat eine schöne Seite über JavaScript Prototypal Inheritance :

Vor fünf Jahren habe ich Classical Inheritance in JavaScript geschrieben. Es zeigte sich, dass JavaScript eine klassenfreie, prototypische Sprache ist und über eine ausreichende Ausdruckskraft verfügt, um ein klassisches System zu simulieren. Mein Programmierstil hat sich seitdem so entwickelt, wie es jeder gute Programmierer tun sollte. Ich habe gelernt, den Prototypalismus voll und ganz zu akzeptieren, und mich von den Grenzen des klassischen Modells befreit.

Dean Edwards Base.js , Mootools's Class oder John Resigs Simple Inheritance- Werke sind Möglichkeiten, klassische Vererbung in JavaScript durchzuführen .


Warum nicht einfach, newObj = Object.create(oldObj);wenn Sie es klassenfrei wollen? Andernfalls sollte das Ersetzen durch oldObjdurch das Prototypobjekt der Konstruktorfunktion funktionieren?
Cyker

76

Wie bereits erwähnt, geben die Filme von Douglas Crockford eine gute Erklärung für das Warum und das Wie. Aber um es in ein paar Zeilen JavaScript auszudrücken:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

Das Problem bei diesem Ansatz ist jedoch, dass das Objekt jedes Mal neu erstellt wird, wenn Sie eines erstellen. Ein anderer Ansatz besteht darin, Ihre Objekte auf dem Prototyp-Stapel wie folgt zu deklarieren:

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

Es gibt einen leichten Nachteil, wenn es um Selbstbeobachtung geht. Wenn Sie testOne ausgeben, erhalten Sie weniger nützliche Informationen. Auch das Privateigentum "privateVariable" in "testOne" wird in allen Fällen gemeinsam genutzt, was in den Antworten von shesek ebenfalls hilfreich erwähnt wird.


3
Beachten Sie, dass in testOne privateVariableeinfach eine Variable im Bereich des IIFE ist und von allen Instanzen gemeinsam genutzt wird, sodass Sie keine instanzspezifischen Daten darauf speichern sollten. (auf testTwo ist es instanzspezifisch, da jeder Aufruf von testTwo () einen neuen Bereich pro Instanz erstellt)
shesek

Ich habe positiv gestimmt, weil Sie den anderen Ansatz gezeigt haben und warum Sie ihn nicht verwenden sollten, weil er Kopien erstellt
Murphy316

Das Problem, das Objekt jedes Mal neu zu erstellen, ist hauptsächlich auf die Methoden zurückzuführen, die für jedes neue Objekt neu erstellt werden. Wir können das Problem jedoch verringern, indem wir die Methode für definieren Dog.prototype. Anstatt zu verwenden this.bark = function () {...}, können wir dies auch Dot.prototype.bark = function () {...}außerhalb der DogFunktion tun . (Weitere Details in dieser Antwort )
Huang Chao

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
Vielleicht könnte das Hinzufügen dieses Links mit Ihrer Antwort das Bild noch weiter vervollständigen: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Dynom

14

Ich würde mir YUI und Dean Edwards BaseBibliothek ansehen : http://dean.edwards.name/weblog/2006/03/base/

Für YUI können Sie einen kurzen Blick auf das lang-Modul werfen , insb. die YAHOO.lang.extend- Methode. Anschließend können Sie die Quelle einiger Widgets oder Dienstprogramme durchsuchen und sehen, wie sie diese Methode verwenden.


YUI 2 ist seit 2011 veraltet, daher ist der Link zu teilweise langunterbrochen. Möchte jemand das Problem für YUI 3 beheben?
Bestätigen Sie den

lang in yui 3 scheint keine Extend-Methode zu haben. Da die Antwort jedoch die Implementierung als Beispiel verwenden soll, spielt die Version keine Rolle.
eMBee


4

Dies ist das klarste Beispiel, das ich aus Mixus Node-Buch ( http://book.mixu.net/node/ch6.html ) gefunden habe:

Ich bevorzuge Komposition gegenüber Vererbung:

Zusammensetzung - Die Funktionalität eines Objekts besteht aus einem Aggregat verschiedener Klassen, indem Instanzen anderer Objekte enthalten werden. Vererbung - Die Funktionalität eines Objekts besteht aus seiner eigenen Funktionalität und der Funktionalität seiner übergeordneten Klassen. Wenn Sie eine Vererbung haben müssen, verwenden Sie einfaches altes JS

Wenn Sie die Vererbung implementieren müssen, vermeiden Sie zumindest die Verwendung einer weiteren nicht standardmäßigen Implementierungs- / Zauberfunktion. So können Sie ein vernünftiges Vererbungsfaksimile in reinem ES3 implementieren (sofern Sie die Regel befolgen, niemals Eigenschaften für Prototypen zu definieren):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

Dies ist nicht dasselbe wie die klassische Vererbung - aber es ist standardmäßiges, verständliches Javascript und verfügt über die Funktionalität, die die meisten Menschen suchen: verkettbare Konstruktoren und die Fähigkeit, Methoden der Oberklasse aufzurufen.


4

ES6 classundextends

ES6 classundextends sind nur Syntaxzucker für bisher mögliche Manipulationen der Prototypenkette und damit wohl das kanonischste Setup.

Weitere .Informationen zur Suche nach Prototypen und Eigenschaften finden Sie zunächst unter: https://stackoverflow.com/a/23877420/895245

Lassen Sie uns nun dekonstruieren, was passiert:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

Vereinfachtes Diagramm ohne alle vordefinierten Objekte:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

Die besten Beispiele, die ich gesehen habe, sind Douglas Crockfords JavaScript: The Good Parts . Es lohnt sich auf jeden Fall zu kaufen, um eine ausgewogene Sicht auf die Sprache zu erhalten.

Douglas Crockford ist für das JSON-Format verantwortlich und arbeitet bei Yahoo als JavaScript-Guru.


7
verantwortlich? das klingt fast wie "schuldig von" :)
Roland Bouman

@ Roland Ich denke, JSON ist ein ziemlich nettes, nicht ausführliches Format zum Speichern von Daten. Er hat es definitiv nicht erfunden, das Format war 2002 für die Konfigurationseinstellungen in Steam da
Chris S

Chris S, ich denke auch - Immer öfter wünschte ich mir, wir hätten alle XML als Austauschformat überspringen und sofort auf JSON umsteigen können.
Roland Bouman

3
Nicht viel zu erfinden: JSON ist eine Teilmenge der JavaScript-eigenen Objektliteral-Syntax, die seit etwa 1997 in der Sprache verfügbar ist.
Tim Down

@Time guter Punkt - Ich wusste nicht, dass es von Anfang an da war
Chris S

0

Es gibt ein Snippet JavaScript Prototype-based Inheritance mit versionierungsspezifischen Implementierungen von ECMAScript. Je nach aktueller Laufzeit wird automatisch ausgewählt, welche zwischen ES6-, ES5- und ES3-Implementierungen verwendet werden soll.


0

Hinzufügen eines Beispiels für eine prototypbasierte Vererbung in Javascript.

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6 verwendet eine weitaus einfachere Implementierung der Vererbung unter Verwendung von Konstruktor- und Super-Schlüsselwörtern.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.