Verwendung von 'Prototyp' vs. 'Dies' in JavaScript?

776

Was ist der Unterschied zwischen

var A = function () {
    this.x = function () {
        //do something
    };
};

und

var A = function () { };
A.prototype.x = function () {
    //do something
};
sw234
quelle
Das Konzept dieses Schlüsselworts wird hier explizit erklärt. scotch.io/@alZami/understanding-this-in-javascript
AL-zami
1
Das Lesen dieses Threads zeigt, wie schrecklich JS ist und wie unklar seine Prinzipien für viele Entwickler sind. Was ist genau falsch an leichter verständlichen Sprachen? Ich denke, es ist an der Zeit, dass Entwickler ihre Stimme erheben, um verwirrende Technologien abzulehnen, die weder für die Geschäfts- noch für die Entwicklungsarbeit von geringem oder geringem Wert sind.
NoChance
Auf Objekt : a1.x !== a2.x; auf Prototyp:a1.x === a2.x
Juan Mendes

Antworten:

467

Die Beispiele haben sehr unterschiedliche Ergebnisse.

Bevor Sie sich die Unterschiede ansehen, sollten Sie Folgendes beachten:

  • Der Prototyp eines Konstruktors bietet eine Möglichkeit, Methoden und Werte über die private [[Prototype]]Eigenschaft der Instanz zwischen Instanzen auszutauschen.
  • Eine Funktion ist diese wird durch , wie die Funktion oder durch die Verwendung von aufgerufen wird , binden (hier nicht beschrieben). Wenn eine Funktion für ein Objekt aufgerufen wird (z. B. myObj.method()) , verweist diese innerhalb der Methode auf das Objekt. Wenn dies nicht durch den Aufruf oder die Verwendung von bind festgelegt wird , wird standardmäßig das globale Objekt (Fenster in einem Browser) verwendet oder bleibt im strengen Modus undefiniert.
  • JavaScript ist eine objektorientierte Sprache, dh die meisten Werte sind Objekte, einschließlich Funktionen. (Zeichenfolgen, Zahlen und Boolesche Werte sind keine Objekte.)

Hier sind also die fraglichen Schnipsel:

var A = function () {
    this.x = function () {
        //do something
    };
};

In diesem Fall wird der Variablen Aein Wert zugewiesen, der auf eine Funktion verweist. Wenn diese Funktion mit aufgerufen wird A(), wird dies nicht durch den Aufruf festgelegt, sodass standardmäßig das globale Objekt verwendet wird und der Ausdruck this.xwirksam ist window.x. Das Ergebnis ist, dass ein Verweis auf den Funktionsausdruck auf der rechten Seite zugewiesen wird window.x.

Im Falle des:

var A = function () { };
A.prototype.x = function () {
    //do something
};

etwas ganz anderes passiert. In der ersten Zeile wird der Variablen Aeine Referenz auf eine Funktion zugewiesen. In JavaScript verfügen alle Funktionsobjekte standardmäßig über eine Prototyp- Eigenschaft, sodass kein separater Code zum Erstellen eines A.prototype- Objekts vorhanden ist.

In der zweiten Zeile wird A.prototype.x eine Referenz auf eine Funktion zugewiesen. Dadurch wird eine x- Eigenschaft erstellt, wenn sie nicht vorhanden ist, oder ein neuer Wert zugewiesen, wenn dies der Fall ist. Der Unterschied zum ersten Beispiel, in dem die x- Eigenschaft des Objekts am Ausdruck beteiligt ist.

Ein weiteres Beispiel ist unten. Es ist ähnlich wie beim ersten (und vielleicht, worüber Sie fragen wollten):

var A = new function () {
    this.x = function () {
        //do something
    };
};

In diesem Beispiel wurde der newOperator vor dem Funktionsausdruck hinzugefügt, damit die Funktion als Konstruktor aufgerufen wird. Beim Aufruf mit newwird die Funktion this so eingestellt, dass sie auf ein neues Objekt verweist, dessen private [[Prototype]]Eigenschaft so eingestellt ist, dass sie auf den öffentlichen Prototyp des Konstruktors verweist . In der Zuweisungsanweisung wird die xEigenschaft für dieses neue Objekt erstellt. Beim Aufruf als Konstruktor gibt eine Funktion ihre zurück dieses Objekt standardmäßig zurück, sodass keine separate return this;Anweisung erforderlich ist .

So überprüfen Sie, ob A eine x- Eigenschaft hat:

console.log(A.x) // function () {
                 //   //do something
                 // };

Dies ist eine ungewöhnliche Verwendung von new, da der Konstruktor nur über A.constructor referenziert werden kann . Es wäre viel üblicher zu tun:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Eine andere Möglichkeit, ein ähnliches Ergebnis zu erzielen, besteht darin, einen sofort aufgerufenen Funktionsausdruck zu verwenden:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

In diesem Fall wird Ader Rückgabewert des Aufrufs der Funktion auf der rechten Seite zugewiesen. Da dies im Aufruf nicht festgelegt ist, verweist es auch hier auf das globale Objekt und this.xist wirksam window.x. Da die Funktion nichts zurückgibt, Ahat sie einen Wert von undefined.

Diese Unterschiede zwischen den beiden Ansätzen zeigen sich auch, wenn Sie Ihre Javascript-Objekte zu / von JSON serialisieren und de-serialisieren. Auf dem Prototyp eines Objekts definierte Methoden werden beim Serialisieren des Objekts nicht serialisiert. Dies kann praktisch sein, wenn Sie beispielsweise nur die Datenteile eines Objekts serialisieren möchten, nicht jedoch die Methoden:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Verwandte Fragen :

Nebenbemerkung: Es gibt möglicherweise keine signifikanten Speichereinsparungen zwischen den beiden Ansätzen. Die Verwendung des Prototyps zum Teilen von Methoden und Eigenschaften benötigt jedoch wahrscheinlich weniger Speicher als jede Instanz mit einer eigenen Kopie.

JavaScript ist keine einfache Sprache. Es ist möglicherweise nicht sehr wertvoll, sich Prototyping oder andere Vererbungsmuster vorzustellen, um die Art und Weise, wie Speicher zugewiesen wird, explizit zu ändern.

Keparo
quelle
49
@keparo: Du liegst falsch. Jedes Objekt hat ein [internes] Prototypobjekt (das sein kann null), das sich jedoch stark von der prototypeEigenschaft unterscheidet, die sich auf Funktionen bezieht und auf die der Prototyp aller Instanzen festgelegt wird, wenn sie mit erstellt werden new. Ich kann nicht glauben, dass dies wirklich 87 positive Stimmen bekommen hat :-(
Bergi
8
"The language is functional"Sind Sie sicher, dass dies funktional bedeutet?
Phant0m
23
Ich stimme dem zu, was @Bergi über Prototypen gesagt hat. Funktionen haben eine Prototyp-Eigenschaft. Alle Objekte, einschließlich Funktionen, verfügen über eine andere interne Eigenschaft, auf die in einigen Browsern mit Object.getPrototypeOf (myObject) oder mit myObject .__ proto__ zugegriffen werden kann. Die Eigenschaft proto gibt das übergeordnete Objekt des Objekts in der Prototypkette an (oder das Objekt, von dem dieses Objekt erbt). Die Prototyp-Eigenschaft (die nur für Funktionen gilt) gibt das Objekt an, das zum übergeordneten Objekt aller Objekte wird, die die Funktion zum Erstellen neuer Objekte mit dem neuen Schlüsselwort verwenden.
Jim Cooper
11
Dieser Artikel ist ziemlich fehlgeleitet und verwirrt, wie dies eingestellt ist. Arbeiten an einem Umschreiben.
RobG
37
Diese Antwort ist ziemlich bizarr und scheint den Punkt der Frage völlig zu verfehlen. Die Frage scheint sehr häufig zu sein, wenn es darum geht, Typeneigenschaften innerhalb des Konstruktors im Vergleich zum Prototyp zu definieren, aber die Hälfte der Antwort bezieht sich darauf, was passieren würde, wenn Sie Aals Funktion verwendet würden, und die andere Hälfte handelt von obskuren und unorthodoxen Methoden etwas unkompliziertes.
JLRishe
235

Wie andere in der ersten Version gesagt haben, führt die Verwendung von "this" dazu, dass jede Instanz der Klasse A eine eigene unabhängige Kopie der Funktionsmethode "x" hat. Während die Verwendung von "Prototyp" bedeutet, dass jede Instanz der Klasse A dieselbe Kopie der Methode "x" verwendet.

Hier ist ein Code, der diesen subtilen Unterschied zeigt:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Wie andere bereits erwähnt haben, gibt es verschiedene Gründe, sich für die eine oder andere Methode zu entscheiden. Meine Stichprobe soll nur den Unterschied deutlich machen.

Benry
quelle
5
Dies ist, was ich erwarten würde, aber wenn ich ein neues Objekt instanziiert habe, nachdem ich Axe wie oben geändert habe, zeige ich immer noch 'A' an, es sei denn, ich verwende A wie einen Singleton. jsbin.com/omida4/2/edit
Quallenbaum
19
Das liegt daran, dass mein Beispiel falsch war. Es ist erst seit zwei Jahren falsch. Seufzer. Aber der Punkt ist immer noch gültig. Ich habe das Beispiel mit einem aktualisiert, das tatsächlich funktioniert. Vielen Dank für den Hinweis.
Benry
4
Es ist eine statische Methode! : D
6
Ja ... 'Prototyp' bedeutet statische Ebene oder Klassenebene. Diese wird von allen erstellten Instanzen gemeinsam genutzt. 'Dies' ist eine Instanzmethode, für die jede Instanz eine eigene Kopie hat
Aneer Dev
7
Es ist nicht statisch. Statisch, wie es in den meisten OO-Sprachen verwendet wird, impliziert, dass keine Abhängigkeit von dem thisObjekt besteht, das der Eigentümer der Methode ist. dh die Methode hat kein Objekt, das ihr Besitzer ist. In diesem Fall gibt es ein thisObjekt, wie im Beispiel in Klasse A gezeigt.
CJStuart
152

Nehmen Sie diese 2 Beispiele:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Die meisten Leute hier (insbesondere die am besten bewerteten Antworten) versuchten zu erklären, wie sie sich unterscheiden, ohne zu erklären, WARUM. Ich denke, das ist falsch und wenn Sie zuerst die Grundlagen verstehen, wird der Unterschied offensichtlich. Versuchen wir zunächst, die Grundlagen zu erklären ...

a) Eine Funktion ist ein Objekt in JavaScript. JEDES Objekt in JavaScript erhält eine interne Eigenschaft (dh Sie können nicht wie andere Eigenschaften darauf zugreifen, außer vielleicht in Browsern wie Chrome), die häufig als bezeichnet wird __proto__(Sie können tatsächlich anyObject.__proto__Chrome eingeben, um zu sehen, worauf es verweist. Dies ist genau das , eine Eigenschaft, nichts weiter. Eine Eigenschaft in JavaScript = eine Variable innerhalb eines Objekts, nichts weiter. Was machen Variablen? Sie zeigen auf Dinge.

Worauf weist diese __proto__Eigenschaft hin? Nun, normalerweise ein anderes Objekt (wir werden später erklären, warum). Die einzige Möglichkeit, JavaScript zu erzwingen, damit die __proto__Eigenschaft NICHT auf ein anderes Objekt verweist, ist die Verwendung var newObj = Object.create(null). Selbst wenn Sie dies tun, existiert die __proto__Eigenschaft STILL als Eigenschaft des Objekts, nur zeigt sie nicht auf ein anderes Objekt, sondern aufnull .

Hier sind die meisten Leute verwirrt:

Wenn Sie eine neue Funktion in JavaScript erstellen (was auch ein Objekt ist, erinnern Sie sich?), Erstellt JavaScript in dem Moment, in dem es definiert wird, automatisch eine neue Eigenschaft für diese Funktion namens prototype. Versuch es:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypeist völlig anders als die __proto__Eigenschaft. In unserem Beispiel hat 'A' jetzt ZWEI Eigenschaften, die als 'Prototyp' und 'bezeichnet werden __proto__. Dies ist eine große Verwirrung für die Menschen. prototypeund __proto__Eigenschaften sind in keiner Weise miteinander verbunden, sie sind separate Dinge, die auf separate Werte verweisen.

Sie fragen sich vielleicht: Warum hat JavaScript __proto__für jedes einzelne Objekt eine Eigenschaft erstellt? Nun, ein Wort: Delegation . Wenn Sie eine Eigenschaft für ein Objekt aufrufen und das Objekt sie nicht hat, sucht JavaScript nach dem Objekt, auf das verwiesen wird, __proto__um festzustellen, ob es möglicherweise vorhanden ist. Wenn es nicht vorhanden ist, wird die __proto__Eigenschaft dieses Objekts usw. überprüft, bis die Kette endet. So der Name Prototypkette . Wenn JavaScript __proto__nicht auf ein Objekt zeigt und stattdessen auf ein nullPech zeigt, erkennt JavaScript dies und gibt Sie undefinedfür die Eigenschaft zurück.

Sie fragen sich vielleicht auch, warum JavaScript eine Eigenschaft erstellt, die prototypefür eine Funktion aufgerufen wird, wenn Sie die Funktion definieren. Weil es versucht, Sie zu täuschen, täuschen Sie, dass es wie klassenbasierte Sprachen funktioniert.

Fahren wir mit unserem Beispiel fort und erstellen ein "Objekt" aus A:

var a1 = new A();

Es passiert etwas im Hintergrund, als dieses Ding passiert ist. a1ist eine gewöhnliche Variable, der ein neues, leeres Objekt zugewiesen wurde.

Die Tatsache, dass Sie den Operator newvor einem Funktionsaufruf verwendet A()haben, hat im Hintergrund etwas ZUSÄTZLICHES bewirkt. Das newSchlüsselwort hat ein neues Objekt erstellt, auf das jetzt verwiesen wird, a1und dieses Objekt ist leer. Folgendes passiert zusätzlich:

Wir haben gesagt, dass für jede Funktionsdefinition eine neue Eigenschaft namens erstellt wird prototype(auf die Sie im Gegensatz zur __proto__Eigenschaft zugreifen können ). Nun, diese Eigenschaft wird jetzt verwendet.

Wir sind jetzt an dem Punkt angelangt, an dem wir ein frisch gebackenes leeres a1Objekt haben. Wir haben gesagt, dass alle Objekte in JavaScript eine interne __proto__Eigenschaft haben, die auf etwas verweist ( a1auch darauf), ob es null oder ein anderes Objekt ist. Der newOperator setzt diese __proto__Eigenschaft so, dass sie auf die Eigenschaft der Funktion verweist prototype. Lesen Sie das noch einmal. Es ist im Grunde das:

a1.__proto__ = A.prototype;

Wir sagten, das A.prototypesei nichts weiter als ein leeres Objekt (es sei denn, wir ändern es vor der Definition in etwas anderes a1). Zeigt nun also im Grunde a1.__proto__auf dasselbe A.prototype, worauf das leere Objekt verweist. Beide zeigen auf dasselbe Objekt, das beim Auftreten dieser Zeile erstellt wurde:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Jetzt passiert noch etwas, wenn die var a1 = new A()Anweisung verarbeitet wird. Grundsätzlich A()wird ausgeführt und wenn A so etwas ist:

var A = function() { this.hey = function() { alert('from A') } };

All das Zeug im Inneren function() { }wird ausgeführt. Wenn Sie die this.hey..Linie erreichen, thiswird in geändert a1und Sie erhalten Folgendes:

a1.hey = function() { alert('from A') }

Ich werde nicht erläutern, warum thisÄnderungen an vorgenommen wurden, a1aber dies ist eine großartige Antwort , um mehr zu erfahren.

Zusammenfassend lässt sich sagen, var a1 = new A()dass im Hintergrund drei Dinge passieren:

  1. Ein völlig neues leeres Objekt wird erstellt und zugewiesen a1.a1 = {}
  2. a1.__proto__Die Eigenschaft wird so zugewiesen, dass sie auf dasselbe zeigt wie A.prototypeauf (ein anderes leeres Objekt {}).

  3. Die Funktion A()wird ausgeführt, indem thisauf das neue, leere Objekt gesetzt wird, das in Schritt 1 erstellt wurde (lesen Sie die Antwort, auf die ich oben verwiesen habe, warum thisÄnderungen an vorgenommen wurden a1).

Versuchen wir nun, ein anderes Objekt zu erstellen:

var a2 = new A();

Die Schritte 1, 2, 3 werden wiederholt. Merkst du etwas Das Schlüsselwort ist wiederholen. Schritt 1: a2wird ein neues leeres Objekt sein, Schritt 2: seine __proto__Eigenschaft zeigt auf dasselbe, was A.prototypeauf und vor allem zeigt, Schritt 3: Funktion A()wird WIEDER ausgeführt, was bedeutet, dass a2eine heyEigenschaft erhalten wird, die eine Funktion enthält. a1und a2haben zwei SEPARATE Eigenschaften benannt, heydie auf 2 SEPARATE Funktionen verweisen! Wir haben jetzt doppelte Funktionen in denselben zwei verschiedenen Objekten, die dasselbe tun, oops ... Sie können sich die Auswirkungen auf den Speicher vorstellen, wenn 1000 Objekte mit erstellt werden new A, nachdem alle Funktionsdeklarationen mehr Speicher benötigen als so etwas wie die Nummer 2. Also Wie verhindern wir das?

Erinnern Sie sich, warum die __proto__Eigenschaft auf jedem Objekt existiert? Wenn Sie also die yoManEigenschaft abrufen a1(die nicht vorhanden ist), wird ihre __proto__Eigenschaft konsultiert. Wenn es sich um ein Objekt handelt (und dies in den meisten Fällen auch ist), wird überprüft, ob es enthält yoManund ob dies nicht der Fall ist. Es wird das Objekt __proto__usw. konsultieren . Wenn dies der Fall ist, wird dieser Eigenschaftswert verwendet und angezeigt.

Also hat sich jemand entschieden, diese Tatsache + die Tatsache zu verwenden, dass beim Erstellen a1die __proto__Eigenschaft auf dasselbe (leere) Objekt A.prototypeverweist und auf Folgendes verweist:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

Cool! Wenn Sie jetzt erstellen a1, werden alle drei oben genannten Schritte erneut durchlaufen, und in Schritt 3 wird nichts ausgeführt, da function A()nichts ausgeführt werden muss. Und wenn wir das tun:

a1.hey

Es wird sehen, dass a1es nicht enthält, heyund es wird sein __proto__Eigenschaftsobjekt überprüfen, um festzustellen, ob es es hat, was der Fall ist.

Mit diesem Ansatz entfernen wir den Teil aus Schritt 3, in dem Funktionen bei jeder neuen Objekterstellung dupliziert werden. Anstelle von a1und a2mit einer separaten heyEigenschaft hat jetzt KEINE von ihnen diese. Was Sie wohl inzwischen selbst herausgefunden haben. Das ist das Schöne ... wenn Sie verstehen __proto__und Function.prototype, werden Fragen wie diese ziemlich offensichtlich sein.

HINWEIS: Einige Leute neigen dazu, die interne Prototype-Eigenschaft nicht als zu bezeichnen __proto__. Ich habe diesen Namen durch den Beitrag verwendet, um ihn klar von der Functional.prototypeEigenschaft als zwei verschiedene Dinge zu unterscheiden.

daremkd
quelle
1
Wirklich gründliche und informative Antwort. Ich habe einige Gedächtnistests mit den obigen Objektstrukturen (A.prototype.hey vs object this.hey) durchgeführt und jeweils 1000 Instanzen erstellt. Der Speicherbedarf für den Objekteigenschaftsansatz war im Vergleich zum Prototyp um etwa 100 KB größer. Ich habe dann eine weitere Funktion mit dem gleichen Zweck hinzugefügt, die "albern" genannt wird, und sie hat sich linear auf 200 kb erhöht. Nicht signifikant, aber auch keine Erdnüsse.
Jookyone
Interessanter ist, dass die Prototypmethode geringfügig langsamer war als die lokal ausgeführte Objekteigenschaftsmethode. Insgesamt bin ich mir nicht sicher, ob Javascript für die Datenmanipulation von Objekten mit einer Nummer über 10.000 verwendet werden sollte, sodass kein Grund besteht, Ansätze aufgrund möglicher Speichereffekte zu ändern. Zu diesem Zeitpunkt sollte die Arbeit auf einen Server ausgelagert werden.
Jookyone
Der Punkt ist __proto__und .prototypesind ganz andere Dinge.
Wayou
1
Ich fühle mich nicht zufrieden nur Sie upvote geben ... Gut gemacht!
Kristianmitk
58

In den meisten Fällen sind sie im Wesentlichen gleich, aber die zweite Version spart Speicher, da für jedes Objekt nur eine Instanz der Funktion anstelle einer separaten Funktion vorhanden ist.

Ein Grund für die Verwendung des ersten Formulars ist der Zugriff auf "private Mitglieder". Zum Beispiel:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Aufgrund der Gültigkeitsregeln von Javascript steht private_var für die dieser.x zugewiesene Funktion zur Verfügung, jedoch nicht außerhalb des Objekts.

Matthew Crumley
quelle
1
In diesem Beitrag: stackoverflow.com/a/1441692/654708 finden Sie ein Beispiel für den Zugriff auf private Mitglieder über Prototypen.
GFoley83
@ GFoley83 Diese Antwort zeigt nicht , dass - die Prototypmethoden nur auf die "öffentlichen" Eigenschaften des angegebenen Objekts zugreifen können. Nur die privilegierten Methoden (nicht im Prototyp) können auf die privaten Mitglieder zugreifen.
Alnitak
27

Im ersten Beispiel wird die Schnittstelle nur für dieses Objekt geändert. Das zweite Beispiel ändert die Schnittstelle für alle Objekte dieser Klasse.

Glenn
quelle
Beide werden die Funktion xfür alle Objekte verfügbar machen, deren Prototyp eine neue Instanz von A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams am
21

Das ultimative Problem bei der Verwendung thisanstelle von prototypebesteht darin, dass der Konstruktor der Basisklasse beim Überschreiben einer Methode weiterhin auf die überschriebene Methode verweist. Bedenken Sie:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

gegen:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Wenn Sie der Meinung sind, dass dies kein Problem ist, hängt es davon ab, ob Sie ohne private Variablen leben können und ob Sie genug Erfahrung haben, um ein Leck zu erkennen, wenn Sie eines sehen. Es ist auch unpraktisch, die Konstruktorlogik nach den Methodendefinitionen zu setzen.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

gegen:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1
Tarkabak
quelle
20

Jedes Objekt ist mit einem Prototypobjekt verknüpft. Beim Versuch, auf eine nicht vorhandene Eigenschaft zuzugreifen, sucht JavaScript im Prototypobjekt des Objekts nach dieser Eigenschaft und gibt sie zurück, falls vorhanden.

Die prototypeEigenschaft eines Funktionskonstruktors bezieht sich auf das Prototypobjekt aller Instanzen, die bei Verwendung mit dieser Funktion erstellt wurden new.


In Ihrem ersten Beispiel fügen Sie xjeder mit der AFunktion erstellten Instanz eine Eigenschaft hinzu .

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

Im zweiten Beispiel fügen Sie dem Prototypobjekt eine Eigenschaft hinzu, auf die alle mit erstellten Instanzen Averweisen.

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

Abschließend wird im ersten Beispiel jeder Instanz eine Kopie der Funktion zugewiesen . Im zweiten Beispiel wird eine einzelne Kopie der Funktion von allen Instanzen gemeinsam genutzt .

pishpish
quelle
1
Wählte dies als die direkteste Antwort auf die Frage.
Nick Pineda
1
Ich mochte Ihren direkten Ansatz! Daumen hoch!
Prinz Vijay Pratap
16

Was ist der Unterschied? => Viel.

Ich denke, die thisVersion wird verwendet, um die Kapselung zu ermöglichen, dh Daten zu verstecken. Es hilft, private Variablen zu manipulieren.

Schauen wir uns das folgende Beispiel an:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Jetzt kann die prototypeStruktur wie folgt angewendet werden:

Unterschiedliche Erwachsene haben unterschiedliche Altersstufen, aber alle Erwachsenen erhalten die gleichen Rechte.
Also fügen wir es eher mit einem Prototyp als mit diesem hinzu.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Schauen wir uns jetzt die Implementierung an.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Hoffe das hilft.

oozzal
quelle
3
+1 Eine viel weniger komplizierte und grafischere Antwort als die anderen. Sie sollten jedoch etwas näher darauf eingehen, bevor Sie diese (guten) Beispiele bereitstellen.
Yerforkferchips
1
Ich bin mir nicht sicher, ob "diese Version zum Aktivieren der Kapselung verwendet wird, dh zum Verstecken von Daten". Wenn eine Eigenschaft innerhalb einer Funktion mit "this" wie in "this.myProperty = ..." definiert wird, ist eine solche Eigenschaft nicht "privat" und kann von Objekten außerhalb der Klasse mit "new" aufgerufen werden.
NoChance
14

Der Prototyp ist die Vorlage der Klasse. das gilt für alle zukünftigen Instanzen davon. Dies ist die besondere Instanz des Objekts.

harropriiz
quelle
14

Ich weiß, dass dies zu Tode beantwortet wurde, aber ich möchte ein aktuelles Beispiel für Geschwindigkeitsunterschiede zeigen.

Funktion direkt am Objekt

Funktion am Prototyp

Hier erstellen wir 2.000.000 neue Objekte mit einer printMethode in Chrome. Wir speichern jedes Objekt in einem Array. Das Anbringen printdes Prototyps dauert etwa halb so lange.

Arnav Aggarwal
quelle
13

Lassen Sie mich eine umfassendere Antwort geben, die ich während eines JavaScript-Schulungskurses gelernt habe.

Die meisten Antworten erwähnten den Unterschied bereits, dh wenn das Prototyping der Funktion mit allen (zukünftigen) Instanzen geteilt wird. Durch das Deklarieren der Funktion in der Klasse wird für jede Instanz eine Kopie erstellt.

Im Allgemeinen gibt es kein Richtig oder Falsch, es ist eher eine Frage des Geschmacks oder einer Designentscheidung, abhängig von Ihren Anforderungen. Der Prototyp ist jedoch die Technik, mit der sich objektorientiert entwickelt, wie Sie hoffentlich am Ende dieser Antwort sehen werden.

Sie haben in Ihrer Frage zwei Muster gezeigt. Ich werde versuchen, zwei weitere zu erklären und gegebenenfalls die Unterschiede zu erklären. Fühlen Sie sich frei zu bearbeiten / erweitern. In allen Beispielen handelt es sich um ein Autoobjekt, das einen Standort hat und sich bewegen kann.

Objektdekoratormuster

Ich bin mir nicht sicher, ob dieses Muster heutzutage noch relevant ist, aber es existiert. Und es ist gut darüber zu wissen. Sie übergeben einfach ein Objekt und eine Eigenschaft an die Dekorationsfunktion. Der Dekorateur gibt das Objekt mit Eigenschaft und Methode zurück.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Funktionsklassen

Eine Funktion in JavaScript ist ein spezialisiertes Objekt. Eine Funktion wird nicht nur aufgerufen, sondern kann auch Eigenschaften wie jedes andere Objekt speichern.

In diesem Fall Carhandelt es sich um eine Funktion ( auch Think Object ), die wie gewohnt aufgerufen werden kann. Es hat eine Eigenschaft methods(die ein Objekt mit einer moveFunktion ist). Wenn Cares aufgerufen wird, wird die extendFunktion aufgerufen, die etwas Magie ausübt und die CarFunktion (Denkobjekt) um die darin definierten Methoden erweitert methods.

Dieses Beispiel ist zwar anders, kommt aber dem ersten Beispiel in der Frage am nächsten.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Prototypische Klassen

Die ersten beiden Muster ermöglichen eine Diskussion der Verwendung von Techniken zum Definieren gemeinsam genutzter Methoden oder von Methoden, die inline im Hauptteil des Konstruktors definiert sind. In beiden Fällen hat jede Instanz ihre eigene moveFunktion.

Das prototypische Muster eignet sich nicht gut für dieselbe Untersuchung, da die gemeinsame Nutzung von Funktionen über eine Prototypdelegation das eigentliche Ziel des prototypischen Musters ist. Wie andere betonten, wird ein besserer Speicherbedarf erwartet.

Es gibt jedoch einen interessanten Punkt zu wissen: Jedes prototypeObjekt verfügt über eine Convenience-Eigenschaft constructor, die auf die Funktion (Denkobjekt) verweist, an die es angehängt wurde.

Zu den letzten drei Zeilen:

In diesem Beispiel wird Carauf das prototypeObjekt verwiesen, das über constructorsich Carselbst verlinkt , dh auf Car.prototype.constructorsich Carselbst. Auf diese Weise können Sie herausfinden, welche Konstruktorfunktion ein bestimmtes Objekt erstellt hat.

amy.constructorDie Suche schlägt fehl und wird daher an delegiert Car.prototype, die über die Konstruktoreigenschaft verfügt. Und so amy.constructorist es auch Car.

Darüber hinaus amyist ein instanceof Car. Der instanceofOperator prüft, ob sich das Prototypobjekt ( Car) des rechten Operanden an einer beliebigen Stelle in der Prototypenkette ( amy) des linken Operanden befindet .

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Einige Entwickler können am Anfang verwirrt sein. Siehe Beispiel unten:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

Der instanceofBediener kehrt zurück false, da Dogder Prototyp nirgendwo in fidoder Prototypenkette gefunden werden kann. fidoist ein einfaches Objekt, das mit einem Objektliteral erstellt wird, dh nur an delegiert Object.prototype.

Pseudoklassische Muster

Dies ist wirklich nur eine andere Form des prototypischen Musters in vereinfachter Form und vertrauter für diejenigen, die beispielsweise in Java programmieren, da es den newKonstruktor verwendet.

Es macht das gleiche wie im prototypischen Muster, es ist nur syntaktischer Zucker über dem prototypischen Muster.

Der Hauptunterschied besteht jedoch darin, dass in JavaScript-Engines Optimierungen implementiert sind, die nur bei Verwendung des pseudoklassischen Musters gelten. Stellen Sie sich das pseudoklassische Muster als wahrscheinlich schnellere Version des prototypischen Musters vor. Die Objektbeziehungen in beiden Beispielen sind gleich.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Schließlich sollte es nicht allzu schwierig sein zu erkennen, wie objektorientierte Programmierung durchgeführt werden kann. Es gibt zwei Abschnitte.

Ein Abschnitt, der allgemeine Eigenschaften / Methoden im Prototyp (Kette) definiert.

Und ein weiterer Abschnitt, in dem Sie die Definitionen einfügen, die die Objekte voneinander unterscheiden ( locVariable in den Beispielen).

Dies ermöglicht es uns, Konzepte wie Superklasse oder Unterklasse in JavaScript anzuwenden.

Fühlen Sie sich frei, hinzuzufügen oder zu bearbeiten. Noch einmal vollständig könnte ich dies vielleicht zu einem Community-Wiki machen.

Ely
quelle
Nicht um einen sehr gründlichen Beitrag zu schreiben, aber ich dachte, OO und prototypische Vererbung seien im Wesentlichen unterschiedliche Denkrichtungen.
Nick Pineda
Sie sind es, aber man kann OO mit verschiedenen Techniken / Gedanken "machen", nicht wahr?
Ely
Ich bin mir nicht sicher. Viele sagen nur, dass die prototypische Philosophie einfach anders ist und viele versuchen, sie mit OO zu vergleichen, weil es die Denkschule ist, an die viele gewöhnt sind.
Nick Pineda
Ich meine, wenn Sie den OO-Stil üben möchten und die Sprache eine Reihe von Techniken bietet, die dazu beitragen, ist dies nicht unbedingt falsch.
Ely
11

Ich glaube, dass @Matthew Crumley Recht hat. Sie sind funktional , wenn nicht strukturell äquivalent. Wenn Sie Firebug verwenden, um die Objekte zu betrachten, die mit erstellt wurden new, können Sie feststellen, dass sie identisch sind. Meine Präferenz wäre jedoch die folgende. Ich vermute, dass es eher so aussieht, wie ich es in C # / Java gewohnt bin. Definieren Sie also die Klasse, definieren Sie die Felder, den Konstruktor und die Methoden.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

BEARBEITEN Wollte nicht bedeuten, dass der Bereich der Variablen privat war, ich wollte nur veranschaulichen, wie ich meine Klassen in Javascript definiere. Der Variablenname wurde geändert, um dies widerzuspiegeln.

Tvanfosson
quelle
2
_instance_var wie in der Eigenschaft initializeund x methods do not refer to the _instance_var` für eine AInstanz, jedoch für eine globale. Verwenden this._instance_varSie diese Option, wenn Sie die _instance_varEigenschaft einer AInstanz verwenden möchten.
Lekensteyn
2
Das Lustige ist, dass Benry auch einen solchen Fehler gemacht hat, der ebenfalls nach zwei Jahren aufgedeckt wurde: p
Lekensteyn
10

Wie in anderen Antworten erläutert, handelt es sich tatsächlich um eine Leistungsüberlegung, da die Funktion im Prototyp mit allen Instanziierungen geteilt wird - und nicht mit der Funktion, die für jede Instanziierung erstellt wird.

Ich habe ein jsperf zusammengestellt, um dies zu zeigen. Es gibt einen dramatischen Unterschied in der Zeit, die zum Instanziieren der Klasse benötigt wird, obwohl dies wirklich nur relevant ist, wenn Sie viele Instanzen erstellen.

http://jsperf.com/functions-in-constructor-vs-prototype

Devgr
quelle
8

Denken Sie an statisch typisierte Sprache, Dinge an prototypesind statisch und Dinge an thissind instanzbezogen.

Wayou
quelle