Javascript, wenn Prototypen verwendet werden sollen


93

Ich würde gerne verstehen, wann es angebracht ist, Prototypmethoden in js zu verwenden. Sollten sie immer verwendet werden? Oder gibt es Fälle, in denen die Verwendung nicht bevorzugt wird und / oder eine Leistungsstrafe entsteht?

Bei der Suche auf dieser Site nach gängigen Methoden für den Namespace in js scheinen die meisten eine nicht auf Prototypen basierende Implementierung zu verwenden: Verwenden Sie einfach ein Objekt oder ein Funktionsobjekt, um einen Namespace zu kapseln.

Aus einer klassenbasierten Sprache stammend, ist es schwierig, keine Parallelen zu ziehen und zu denken, dass Prototypen wie "Klassen" sind und die von mir erwähnten Namespace-Implementierungen wie statische Methoden sind.

Antworten:


133

Prototypen sind eine Optimierung .

Ein gutes Beispiel für eine gute Verwendung ist die jQuery-Bibliothek. Jedes Mal, wenn Sie ein jQuery-Objekt mithilfe von erhalten $('.someClass'), verfügt dieses Objekt über Dutzende von "Methoden". Die Bibliothek könnte dies erreichen, indem sie ein Objekt zurückgibt:

return {
   show: function() { ... },
   hide: function() { ... },
   css: function() { ... },
   animate: function() { ... },
   // etc...
};

Dies würde jedoch bedeuten, dass jedes jQuery-Objekt im Speicher immer wieder Dutzende von benannten Slots enthält, die dieselben Methoden enthalten.

Stattdessen werden diese Methoden in einem Prototyp definiert und alle jQuery-Objekte "erben" diesen Prototyp, um alle diese Methoden zu sehr geringen Laufzeitkosten zu erhalten.

Ein äußerst wichtiger Teil dessen, wie jQuery es richtig macht, ist, dass dies vor dem Programmierer verborgen ist. Es handelt sich lediglich um eine Optimierung, nicht um etwas, über das Sie sich bei der Verwendung der Bibliothek Sorgen machen müssen.

Das Problem mit JavaScript besteht darin, dass der Aufrufer bei nackten Konstruktorfunktionen daran denken muss, ihnen ein Präfix voranzustellen, newda sie sonst normalerweise nicht funktionieren. Dafür gibt es keinen guten Grund. jQuery macht es richtig, indem es diesen Unsinn hinter einer normalen Funktion versteckt $, sodass Sie sich nicht darum kümmern müssen, wie die Objekte implementiert werden.

Damit Sie bequem ein Objekt mit einem bestimmten Prototyp erstellen können, enthält ECMAScript 5 eine Standardfunktion Object.create. Eine stark vereinfachte Version würde folgendermaßen aussehen:

Object.create = function(prototype) {
    var Type = function () {};
    Type.prototype = prototype;
    return new Type();
};

Es kümmert sich nur um den Schmerz, eine Konstruktorfunktion zu schreiben und sie dann mit aufzurufen new.

Wann würden Sie Prototypen vermeiden?

Ein nützlicher Vergleich ist mit gängigen OO-Sprachen wie Java und C #. Diese unterstützen zwei Arten der Vererbung:

  • Schnittstelle Vererbung, wo Sie implementein , interfaceso dass die Klasse seine eigene einzigartige Implementierung für jedes Mitglied der Schnittstelle zur Verfügung stellt.
  • Implementierungsvererbung , bei der Sie extendeine classStandardimplementierung einiger Methoden bereitstellen.

In JavaScript ist die prototypische Vererbung eine Art Implementierungsvererbung . In Situationen, in denen Sie (in C # oder Java) von einer Basisklasse abgeleitet hätten, um ein Standardverhalten zu erhalten, an dem Sie über Überschreibungen kleine Änderungen vornehmen, ist in JavaScript eine prototypische Vererbung sinnvoll.

Wenn Sie sich jedoch in einer Situation befinden, in der Sie Schnittstellen in C # oder Java verwendet hätten, benötigen Sie in JavaScript keine bestimmte Sprachfunktion. Es ist nicht erforderlich, explizit etwas zu deklarieren, das die Schnittstelle darstellt, und Objekte müssen nicht als "Implementierung" dieser Schnittstelle markiert werden:

var duck = {
    quack: function() { ... }
};

duck.quack(); // we're satisfied it's a duck!

Mit anderen Worten, wenn jeder "Typ" eines Objekts seine eigenen Definitionen der "Methoden" hat, hat das Erben von einem Prototyp keinen Wert. Danach hängt es davon ab, wie viele Instanzen Sie jedem Typ zuweisen. In vielen modularen Designs gibt es jedoch nur eine Instanz eines bestimmten Typs.

Tatsächlich wurde von vielen Leuten vorgeschlagen, dass die Vererbung von Implementierungen böse ist . Das heißt, wenn es einige allgemeine Operationen für einen Typ gibt, ist es vielleicht klarer, wenn sie nicht in eine Basis- / Superklasse eingefügt werden, sondern nur als normale Funktionen in einem Modul verfügbar gemacht werden, an das Sie die Objekte übergeben. Sie möchten, dass sie operieren.


1
Gute Erklärung. Würden Sie dann zustimmen, dass Prototypen, da Sie sie als Optimierung betrachten, immer zur Verbesserung Ihres Codes verwendet werden können? Ich frage mich, ob es Fälle gibt, in denen die Verwendung von Prototypen keinen Sinn ergibt oder tatsächlich zu Leistungseinbußen führt.
opl

In Ihrem Follow-up erwähnen Sie, dass "es davon abhängt, wie viele Instanzen Sie jedem Typ zuweisen". In dem Beispiel, auf das Sie sich beziehen, werden jedoch keine Prototypen verwendet. Wo ist der Begriff der Zuweisung einer Instanz (würden Sie hier immer noch "neu" verwenden)? Außerdem: Angenommen, die Quacksalbermethode hatte einen Parameter - würde jeder Aufruf von duck.quack (param) dazu führen, dass ein neues Objekt im Speicher erstellt wird (möglicherweise ist es irrelevant, ob es einen Parameter hat oder nicht)?
opl

3
1. Ich meinte, wenn es eine große Anzahl von Instanzen eines Ententyps gäbe, wäre es sinnvoll, das Beispiel so zu ändern, dass sich die quackFunktion in einem Prototyp befindet, mit dem die vielen Enteninstanzen verknüpft sind. 2. Die Objektliteral-Syntax { ... }erstellt eine Instanz (es ist nicht erforderlich, sie zu verwenden new). 3. Wenn Sie eine Funktion JS argumentsaufrufen, wird mindestens ein Objekt im Speicher erstellt - es wird als Objekt bezeichnet und speichert die im Aufruf übergebenen Argumente: developer.mozilla.org/en/JavaScript/Reference/…
Daniel Earwicker

Danke, ich habe deine Antwort angenommen. Aber ich habe immer noch leichte Verwirrung mit Ihrem Punkt (1): Ich verstehe nicht, was Sie unter "einer großen Anzahl von Instanzen einer Entensorte" verstehen. Wie Sie in (3) gesagt haben, wird jedes Mal, wenn Sie eine JS-Funktion aufrufen, ein Objekt im Speicher erstellt. Selbst wenn Sie nur einen Ententyp haben, würden Sie nicht jedes Mal Speicher zuweisen, wenn Sie eine Entenfunktion (in) aufrufen In welchem ​​Fall wäre es immer sinnvoll, einen Prototyp zu verwenden?
opl

11
+1 Der Vergleich mit jQuery war die erste klare und präzise Erklärung, wann und warum ich Prototypen verwenden sollte, die ich gelesen habe. Vielen Dank.
GFoley83

46

Sie sollten Prototypen verwenden, wenn Sie eine "nicht statische" Methode des Objekts deklarieren möchten.

var myObject = function () {

};

myObject.prototype.getA = function (){
  alert("A");
};

myObject.getB = function (){
  alert("B");
};

myObject.getB();  // This works fine

myObject.getA();  // Error!

var myPrototypeCopy = new myObject();
myPrototypeCopy.getA();  // This works, too.

@keatsKelleher, aber wir können eine nicht statische Methode für das Objekt erstellen, indem wir nur die Methode innerhalb der Konstruktorfunktion anhand eines thisBeispiels definieren, this.getA = function(){alert("A")}oder?
Amr Labib

17

Ein Grund für die Verwendung des integrierten prototypeObjekts besteht darin, dass Sie ein Objekt mehrmals duplizieren, um gemeinsame Funktionen zu nutzen. Durch Anhängen von Methoden an den Prototyp können Sie beim Duplizieren von Methoden sparen, die pro newInstanz erstellt werden. Aber wenn Sie eine Methode an die anhängenprototype , haben alle Instanzen Zugriff auf diese Methoden.

Angenommen, Sie haben eine Basisklasse Car()/ ein Basisobjekt.

function Car() {
    // do some car stuff
}

Dann erstellen Sie mehrere Car()Instanzen.

var volvo = new Car(),
    saab = new Car();

Jetzt wissen Sie, dass jedes Auto fahren, einschalten usw. muss. Anstatt eine Methode direkt an die Car()Klasse anzuhängen (die pro erstellter Instanz Speicherplatz beansprucht), können Sie die Methoden stattdessen an den Prototyp anhängen (nur die Methoden erstellen) einmal), wodurch sowohl den neuen volvoals auch den neuen Zugang zu diesen Methoden gewährt wird saab.

// just mapping for less typing
Car.fn = Car.prototype;

Car.fn.drive = function () {
    console.log("they see me rollin'");
};
Car.fn.honk = function () {
    console.log("HONK!!!");
}

volvo.honk();
// => HONK!!!
saab.drive();
// => they see me rollin'

2
eigentlich ist das falsch. volvo.honk () funktioniert nicht, da Sie das Prototypobjekt vollständig ersetzt und nicht erweitert haben. Wenn Sie so etwas tun würden, würde es wie erwartet funktionieren: Car.prototype.honk = function () {console.log ('HONK');} volvo.honk (); // 'HONK'
29er

1
@ 29er - so wie ich dieses Beispiel geschrieben habe, bist du richtig. Die Reihenfolge spielt eine Rolle. Wenn ich dieses Beispiel so lassen Car.prototype = { ... }würde, wie es ist, müsste das kommen, bevor new Car()ich ein aufrufe, wie in dieser jsfiddle dargestellt: jsfiddle.net/mxacA . Für Ihr Argument wäre dies der richtige Weg: jsfiddle.net/Embnp . Lustige Sache ist, ich erinnere mich nicht, diese Frage beantwortet zu haben =)
hellatan

@hellatan Sie können dies beheben, indem Sie den Konstruktor: Car auf setzen, da Sie die Prototyp-Eigenschaft mit einem Objektliteral überschrieben haben.
Josh Bedo

@ Josh, danke, dass du darauf hingewiesen hast. Ich habe meine Antwort aktualisiert, damit ich den Prototyp nicht mit einem Objektliteral überschreibe, wie es von Anfang an hätte sein sollen.
Hellatan

12

Fügen Sie einem Prototypobjekt Funktionen hinzu, wenn Sie viele Kopien eines bestimmten Objekttyps erstellen möchten, und alle müssen gemeinsame Verhaltensweisen aufweisen. Auf diese Weise sparen Sie Speicherplatz, indem Sie nur eine Kopie jeder Funktion haben. Dies ist jedoch nur der einfachste Vorteil.

Durch Ändern von Methoden für Prototypobjekte oder Hinzufügen von Methoden wird sofort die Art aller Instanzen der entsprechenden Typen geändert.

Warum genau Sie all diese Dinge tun würden, hängt hauptsächlich von Ihrem eigenen Anwendungsdesign und den Dingen ab, die Sie im clientseitigen Code ausführen müssen. (Eine ganz andere Geschichte wäre Code innerhalb eines Servers; viel einfacher vorstellbar, dort größeren "OO" -Code zu verwenden.)


Wenn ich also ein neues Objekt mit Prototypmethoden (über ein neues Schlüsselwort) instanziiere, erhält dieses Objekt dann keine neue Kopie jeder Funktion (nur eine Art Zeiger)? Wenn dies der Fall ist, warum möchten Sie dann keinen Prototyp verwenden?
opl

wie @marcel, d'oh ... =)
hellatan

@opi ja, du hast recht - es wird keine Kopie gemacht. Stattdessen sind die Symbole (Eigenschaftsnamen) auf dem Prototypobjekt als virtuelle Teile jedes Instanzobjekts auf natürliche Weise "da". Der einzige Grund, warum sich die Leute nicht darum kümmern möchten, sind Fälle, in denen Objekte kurzlebig und verschieden sind oder in denen es nicht viel "Verhalten" zu teilen gibt.
Pointy

3

Wenn ich in klassenbasierten Begriffen erkläre, dann ist Person Klasse, walk () ist Prototypmethode. Walk () wird also erst dann existieren, wenn Sie damit ein neues Objekt instanziieren.

Wenn Sie also Kopien von Objekten wie Person u erstellen möchten, können Sie viele Benutzer erstellen. Prototype ist eine gute Lösung, da es Speicher spart, indem für jedes Objekt im Speicher dieselbe Funktionskopie freigegeben / geerbt wird.

Während statisch in einem solchen Szenario keine große Hilfe ist.

function Person(){
this.name = "anonymous";
}

// its instance method and can access objects data data 
Person.prototype.walk = function(){
alert("person has started walking.");
}
// its like static method
Person.ProcessPerson = function(Person p){
alert("Persons name is = " + p.name);
}

var userOne = new Person();
var userTwo = new Person();

//Call instance methods
userOne.walk();

//Call static methods
Person.ProcessPerson(userTwo);

Damit ist es eher eine Instanzmethode. Der Ansatz des Objekts ähnelt statischen Methoden.

https://developer.mozilla.org/en/Introduction_to_Object-Oriented_JavaScript

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.