Wie funktioniert JavaScript .prototype?


2041

Ich mag keine dynamischen Programmiersprachen, aber ich habe meinen fairen Anteil an JavaScript-Code geschrieben. Ich habe mich nie wirklich mit dieser prototypbasierten Programmierung beschäftigt. Weiß jemand, wie das funktioniert?

var obj = new Object();
obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Ich erinnere mich an viele Diskussionen, die ich vor einiger Zeit mit Leuten geführt habe (ich bin mir nicht ganz sicher, was ich tue), aber so wie ich es verstehe, gibt es kein Konzept für eine Klasse. Es ist nur ein Objekt, und Instanzen dieser Objekte sind Klone des Originals, oder?

Aber was genau ist der Zweck dieser ".prototype" -Eigenschaft in JavaScript? Wie hängt es mit der Instanziierung von Objekten zusammen?

Update: richtiger Weg

var obj = new Object(); // not a functional object
obj.prototype.test = function() { alert('Hello?'); }; // this is wrong!

function MyObject() {} // a first class functional object
MyObject.prototype.test = function() { alert('OK'); } // OK

Auch diese Folien haben sehr geholfen.


78
John Resig hat einige Folien zu Funktionsprototypen, die mir beim Betrachten des Themas hilfreich waren (Sie können auch Änderungen am Code vornehmen und sehen, was passiert ...) http://ejohn.org/apps/learn/#64
John Foster

5
Tolles Referenzmaterial, um diese Frage informativ zu halten, platzieren Sie möglicherweise einige der Kommentare von Johns Website auf Ihrer Antwort, falls sich seine Website so ändert, dass Ihr Link nicht mehr verfügbar ist. So oder so +1, hat mir geholfen.
Chris

95
+1 für Ihren Link zu John Resigs JavaScript Ninja Folie Nr. 64 . Von dort aus war es sehr hilfreich und ich habe das Gefühl, Prototypen richtig zu verstehen.
Ein bezahlter Nerd

4
Benötigen wir wirklich ein Funktionsobjekt für die Anwendung des Prototyps? wenn ja als warum?
Anshul

6
Dies könnte Ihnen helfen: webdeveasy.com/javascript-prototype
Naor

Antworten:


1007

Jedes JavaScript-Objekt hat einen internen "Slot" , [[Prototype]]dessen Wert entweder nulloder ein ist object. Sie können sich einen Slot als eine Eigenschaft eines Objekts vorstellen, das sich innerhalb der JavaScript-Engine befindet und vor dem von Ihnen geschriebenen Code verborgen ist. Die eckigen Klammern [[Prototype]]sind absichtlich und stellen eine ECMAScript-Spezifikationskonvention dar, um interne Steckplätze zu kennzeichnen.

Der Wert, auf den [[Prototype]]ein Objekt zeigt, wird umgangssprachlich als "Prototyp dieses Objekts" bezeichnet.

Wenn Sie über die Notation dot ( obj.propName) oder bracket ( obj['propName']) auf eine Eigenschaft zugreifen und das Objekt nicht direkt über eine solche Eigenschaft verfügt (dh über eine eigene Eigenschaft , die über überprüft werden kann obj.hasOwnProperty('propName')), sucht die Laufzeit auf dem referenzierten Objekt nach einer Eigenschaft mit diesem Namen von der [[Prototype]]stattdessen. Wenn der [[Prototype]] auch keine solche Eigenschaft hat, [[Prototype]]wird diese der Reihe nach überprüft und so weiter. Auf diese Weise wird die Prototypkette des Originalobjekts durchlaufen, bis eine Übereinstimmung gefunden oder das Ende erreicht ist. An der Spitze der Prototypenkette steht der nullWert.

Moderne JavaScript-Implementierungen ermöglichen Lese- und / oder Schreibzugriff auf [[Prototype]]Folgendes:

  1. Der newOperator (konfiguriert die Prototypkette für das Standardobjekt, das von einer Konstruktorfunktion zurückgegeben wird),
  2. Das extendsSchlüsselwort (konfiguriert die Prototypkette bei Verwendung der Klassensyntax),
  3. Object.createsetzt das angegebene Argument als das [[Prototype]]des resultierenden Objekts,
  4. Object.getPrototypeOfund Object.setPrototypeOf( [[Prototype]] nach der Objekterstellung abrufen / setzen ) und
  5. Die Eigenschaft des standardisierten Accessors (dh Getter / Setter) mit dem Namen __proto__(ähnlich wie 4.)

Object.getPrototypeOfund Object.setPrototypeOfwerden teilweise deshalb bevorzugt __proto__, weil das Verhalten von o.__proto__ ungewöhnlich ist, wenn ein Objekt einen Prototyp von hat null.

Ein Objekt [[Prototype]]wird anfänglich während der Objekterstellung festgelegt.

Wenn Sie ein neues Objekt über erstellen new Func(), wird das Objekt [[Prototype]]standardmäßig auf das Objekt gesetzt, auf das verwiesen wird Func.prototype.

Beachten Sie daher, dass alle Klassen und alle Funktionen, die mit dem newOperator verwendet werden können, .prototypezusätzlich zu ihrem eigenen [[Prototype]]internen Steckplatz eine Eigenschaft benannt haben . Diese doppelte Verwendung des Wortes "Prototyp" ist die Quelle endloser Verwirrung unter Neulingen in der Sprache.

Die Verwendung newmit Konstruktorfunktionen ermöglicht es uns, die klassische Vererbung in JavaScript zu simulieren. Obwohl das Vererbungssystem von JavaScript - wie wir gesehen haben - prototypisch und nicht klassenbasiert ist.

Vor der Einführung der Klassensyntax in JavaScript waren Konstruktorfunktionen die einzige Möglichkeit, Klassen zu simulieren. Wir können uns Eigenschaften des Objekts vorstellen, auf das durch die Eigenschaft der Konstruktorfunktion verwiesen wird, .prototypeals gemeinsam genutzte Elemente. dh. Mitglieder, die für jede Instanz gleich sind. In klassenbasierten Systemen werden Methoden für jede Instanz auf dieselbe Weise implementiert, sodass Methoden konzeptionell zur .prototypeEigenschaft hinzugefügt werden . Die Felder eines Objekts sind jedoch instanzspezifisch und werden daher während der Erstellung dem Objekt selbst hinzugefügt.

Ohne die Klassensyntax mussten Entwickler die Prototypenkette manuell konfigurieren, um eine ähnliche Funktionalität wie bei der klassischen Vererbung zu erzielen. Dies führte zu einem Übergewicht verschiedener Wege, um dies zu erreichen.

Hier ist eine Möglichkeit:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
  child.prototype = Object.create(parent.prototype)
  child.prototype.constructor = child
  return child;
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... und hier ist ein anderer Weg:

function Child() {}
function Parent() {}
Parent.prototype.inheritedMethod = function () { return 'this is inherited' }

function inherit(child, parent) {
    function tmp() {}
    tmp.prototype = parent.prototype
    const proto = new tmp()
    proto.constructor = child
    child.prototype = proto
    return child
}

Child = inherit(Child, Parent)
const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

Die in ES2015 eingeführte Klassensyntax vereinfacht die Dinge, indem sie extendsals " einzig wahre Möglichkeit" die Konfiguration der Prototypenkette bereitstellt , um die klassische Vererbung in JavaScript zu simulieren.

Ähnlich wie im obigen Code, wenn Sie die Klassensyntax verwenden, um ein neues Objekt wie folgt zu erstellen:

class Parent { inheritedMethod() { return 'this is inherited' } }
class Child extends Parent {}

const o = new Child
console.log(o.inheritedMethod()) // 'this is inherited'

... das resultierende Objekt [[Prototype]]wird auf eine Instanz von gesetzt Parent, deren [[Prototype]]wiederum ist Parent.prototype.

Wenn Sie schließlich ein neues Objekt über erstellen Object.create(foo), werden die resultierenden Objekte auf [[Prototype]]gesetzt foo.


1
Also mache ich etwas falsch, indem ich neue Eigenschaften für die Prototyp-Eigenschaft in meinem kurzen Snippet definiere?
John Leidegren

3
Ich denke, das ist es, was es bedeutet, Funktionsobjekte als erstklassige Bürger zu haben.
John Leidegren

8
Ich hasse nicht standardmäßige Dinge, besonders in Programmiersprachen. Warum gibt es überhaupt ein Proto, wenn es eindeutig nicht benötigt wird?
John Leidegren

1
@John __proto__ wird nur vom JS-Interpreter für den internen Gebrauch benötigt. Jedes Objekt muss wissen, welche Eigenschaften und Methoden Teil des Prototyps und welche Teil des Objekts selbst sind. Der JS-Interpreter muss in der Lage sein, prototypisierte Methoden für ein Objekt aufzurufen.
BMiner

17
Beachten Sie, dass die Verwendung von [[Prototype]] absichtlich ist - ECMA-262 schließt Namen von internen Eigenschaften mit doppelten eckigen Klammern ein
Christoph

1798

In einer Sprache, die klassische Vererbung wie Java, C # oder C ++ implementiert, erstellen Sie zunächst eine Klasse - eine Blaupause für Ihre Objekte - und können dann neue Objekte aus dieser Klasse erstellen oder die Klasse erweitern und eine neue Klasse definieren, die erweitert wird die ursprüngliche Klasse.

In JavaScript erstellen Sie zuerst ein Objekt (es gibt kein Klassenkonzept), dann können Sie Ihr eigenes Objekt erweitern oder neue Objekte daraus erstellen. Es ist nicht schwierig, aber ein wenig fremd und schwer zu metabolisieren für jemanden, der an die klassische Art gewöhnt ist.

Beispiel:

//Define a functional object to hold persons in JavaScript
var Person = function(name) {
  this.name = name;
};

//Add dynamically to the already defined object a new getter
Person.prototype.getName = function() {
  return this.name;
};

//Create a new object of type Person
var john = new Person("John");

//Try the getter
alert(john.getName());

//If now I modify person, also John gets the updates
Person.prototype.sayMyName = function() {
  alert('Hello, my name is ' + this.getName());
};

//Call the new method on john
john.sayMyName();

Bis jetzt habe ich das Basisobjekt erweitert, jetzt erstelle ich ein anderes Objekt und erbe dann von Person.

//Create a new object of type Customer by defining its constructor. It's not 
//related to Person for now.
var Customer = function(name) {
    this.name = name;
};

//Now I link the objects and to do so, we link the prototype of Customer to 
//a new instance of Person. The prototype is the base that will be used to 
//construct all new instances and also, will modify dynamically all already 
//constructed objects because in JavaScript objects retain a pointer to the 
//prototype
Customer.prototype = new Person();     

//Now I can call the methods of Person on the Customer, let's try, first 
//I need to create a Customer.
var myCustomer = new Customer('Dream Inc.');
myCustomer.sayMyName();

//If I add new methods to Person, they will be added to Customer, but if I
//add new methods to Customer they won't be added to Person. Example:
Customer.prototype.setAmountDue = function(amountDue) {
    this.amountDue = amountDue;
};
Customer.prototype.getAmountDue = function() {
    return this.amountDue;
};

//Let's try:       
myCustomer.setAmountDue(2000);
alert(myCustomer.getAmountDue());

Wie gesagt, ich kann setAmountDue () nicht aufrufen, getAmountDue () für eine Person.

//The following statement generates an error.
john.setAmountDue(1000);

352
Ich denke, die Antworten auf stackoverflow sind nicht nur für das Originalplakat interessant, sondern auch für eine große Community anderer Leute, die lauern oder von Suchanfragen kommen. Und ich war einer von ihnen und hatte von alten Beiträgen profitiert. Ich denke, ich könnte zu den anderen Antworten beitragen und einige Codebeispiele hinzufügen. Zu Ihrer Frage: Wenn Sie das Neue weglassen, funktioniert es nicht. Wenn ich myCustomer.sayMyName () aufrufe, wird "myCustomer.sayMyName ist keine Funktion" zurückgegeben. Am einfachsten ist es, mit Firebug zu experimentieren und zu sehen, was passiert.
Stivlo

7
Soweit ich verstehe var Person = Funktion (Name) {...}; definiert eine Konstruktorfunktion, mit der Personenobjekte erstellt werden können. Es gibt also noch kein Objekt, nur die anonyme Konstruktorfunktion wird Person zugewiesen. Dies ist eine sehr gute Erklärung: helephant.com/2008/08/how-javascript-objects-work
stivlo

17
WARNUNG: Diese Antwort vernachlässigt die Tatsache, dass der übergeordnete Klassenkonstruktor nicht pro Instanz aufgerufen wird. Der einzige Grund, warum es funktioniert, ist, dass er sowohl im untergeordneten als auch im übergeordneten Konstruktor genau dasselbe getan hat (den Namen festlegen). Eine ausführlichere Erklärung zu häufigen Fehlern beim Versuch der Vererbung in JavaScript (und eine endgültige Lösung) finden Sie unter: Dieser Stapelüberlaufbeitrag
Aaren Cordova,

3
Ich stelle fest, dass in dieser Antwort auch nicht erwähnt wird, dass Sie durch die Verwendung von "new Person ()" als Prototyp die Instanzeigenschaft "name" von "Person" als statische Eigenschaft von "Customer" (also allen Kunden) festlegen Instanzen haben die gleiche Eigenschaft). Während es ein gutes grundlegendes Beispiel ist, tun Sie das nicht. :) Erstellen Sie eine neue anonyme Funktion, die als "Brücke" fungiert, indem Sie den Prototyp auf "Person.prototype" setzen, dann eine Instanz daraus erstellen und stattdessen "Customer.prototype" auf diese anonyme Instanz setzen.
James Wilkins

10
In Bezug auf die Customer.prototype = new Person();Zeile zeigt MDN ein Beispiel mit Customer.prototype = Object.create(Person.prototype)und gibt an, dass 'Ein häufiger Fehler hier die Verwendung von "new Person ()" ist . Quelle
Rafael Eyng

186

Dies ist ein sehr einfaches prototypbasiertes Objektmodell, das während der Erklärung als Beispiel betrachtet wird, ohne dass ein Kommentar vorliegt:

function Person(name){
    this.name = name;
}
Person.prototype.getName = function(){
    console.log(this.name);
}
var person = new Person("George");

Es gibt einige entscheidende Punkte, die wir berücksichtigen müssen, bevor wir das Prototypkonzept durchgehen.

1- Wie JavaScript-Funktionen tatsächlich funktionieren:

Um den ersten Schritt zu machen, müssen wir herausfinden, wie JavaScript-Funktionen tatsächlich funktionieren, als klassenähnliche Funktion, die ein thisSchlüsselwort verwendet, oder einfach als reguläre Funktion mit ihren Argumenten, was sie tut und was sie zurückgibt.

Angenommen, wir möchten ein PersonObjektmodell erstellen . Aber in diesem Schritt werde ich versuchen, genau das Gleiche zu tun, ohne prototypeund zu verwendennew Stichwort .

Also in diesem Schritt functions, objectsundthis Stichwort, ist alles , was wir haben.

Die erste Frage wäre, wie ein thisSchlüsselwort ohne Verwendung eines newSchlüsselworts nützlich sein könnte .

Um das zu beantworten, nehmen wir an, wir haben ein leeres Objekt und zwei Funktionen wie:

var person = {};
function Person(name){  this.name = name;  }

function getName(){
    console.log(this.name);
}

und jetzt ohne zu benutzennew Schlüsselwort, wie wir diese Funktionen verwenden könnten. JavaScript bietet dazu drei verschiedene Möglichkeiten:

ein. Der erste Weg besteht darin, die Funktion als reguläre Funktion aufzurufen:

Person("George");
getName();//would print the "George" in the console

In diesem Fall ist dies das aktuelle Kontextobjekt, bei dem es sich normalerweise um das globale windowObjekt im Browser oder handeltGLOBAL in handelt Node.js. Dies bedeutet, dass wir window.name im Browser oder GLOBAL.name in Node.js mit "George" als Wert haben würden.

b. Wir können anhängen sie als ihre Eigenschaften an ein Objekt

- Der einfachste Weg , dies zu tun, besteht darin, das leere personObjekt zu ändern, wie:

person.Person = Person;
person.getName = getName;

Auf diese Weise können wir sie wie folgt nennen:

person.Person("George");
person.getName();// -->"George"

und jetzt ist das personObjekt wie:

Object {Person: function, getName: function, name: "George"}

- Die andere Möglichkeit, eine Eigenschaft an ein Objekt anzuhängen , besteht darin, das prototypeObjekt zu verwenden, das in jedem JavaScript-Objekt mit dem Namen "zu finden" __proto__ist. Ich habe versucht, es im Zusammenfassungsteil ein wenig zu erläutern. So könnten wir das gleiche Ergebnis erzielen, indem wir Folgendes tun:

person.__proto__.Person = Person;
person.__proto__.getName = getName;

Auf diese Weise ändern wir jedoch das Object.prototype, denn wenn wir ein JavaScript-Objekt mit literals ( { ... }) erstellen , wird es basierend auf erstellt Object.prototype, was bedeutet, dass es als Attribut mit dem Namen an das neu erstellte Objekt angehängt wird. __proto__Wenn wir es also ändern Wie bei unserem vorherigen Code-Snippet würden alle JavaScript-Objekte geändert, was keine gute Vorgehensweise ist. Was könnte jetzt die bessere Praxis sein:

person.__proto__ = {
    Person: Person,
    getName: getName
};

und jetzt sind andere Objekte in Frieden, aber es scheint immer noch keine gute Praxis zu sein. Wir haben also noch eine weitere Lösung, aber um diese Lösung zu verwenden, sollten wir zu der Codezeile zurückkehren, in der das personObjekt erstellt wurde ( var person = {};), und sie dann wie folgt ändern:

var propertiesObject = {
    Person: Person,
    getName: getName
};
var person = Object.create(propertiesObject);

Es wird ein neues JavaScript erstellt Objectund das propertiesObjectan das __proto__Attribut angehängt. So stellen Sie sicher, dass Sie Folgendes tun können:

console.log(person.__proto__===propertiesObject); //true

Der schwierige Punkt hierbei ist jedoch, dass Sie Zugriff auf alle Eigenschaften haben, die __proto__auf der ersten Ebene des personObjekts definiert sind (lesen Sie den Zusammenfassungsteil für weitere Einzelheiten).


Wie Sie sehen, würde die Verwendung einer dieser beiden Möglichkeiten thisgenau auf die zeigenperson Objekt zeigen.

c. JavaScript bietet eine andere Möglichkeit, die Funktion bereitzustellen this, nämlich Aufruf oder Anwenden , um die Funktion aufzurufen.

Die Methode apply () ruft eine Funktion mit einem bestimmten Wert und Argumenten auf, die als Array (oder als Array-ähnliches Objekt) bereitgestellt werden.

und

Die Methode call () ruft eine Funktion mit einem bestimmten Wert und Argumenten auf, die einzeln angegeben werden.

Auf diese Weise, die mein Favorit ist, können wir unsere Funktionen leicht aufrufen wie:

Person.call(person, "George");

oder

//apply is more useful when params count is not fixed
Person.apply(person, ["George"]);

getName.call(person);   
getName.apply(person);

Diese drei Methoden sind die wichtigsten ersten Schritte, um die .prototype-Funktionalität herauszufinden.


2- Wie funktioniert das newSchlüsselwort?

Dies ist der zweite Schritt, um die .prototypeFunktionalität zu verstehen. Dies ist, was ich verwende, um den Prozess zu simulieren:

function Person(name){  this.name = name;  }
my_person_prototype = { getName: function(){ console.log(this.name); } };

In diesem Teil werde ich versuchen, alle Schritte auszuführen, die JavaScript unternimmt, ohne das newSchlüsselwort zu verwenden und prototypewenn Sie das newSchlüsselwort verwenden. Wenn wir dies tun new Person("George"), Persondient die Funktion als Konstruktor. Dies ist, was JavaScript nacheinander tut:

ein. Zunächst wird ein leeres Objekt erstellt, im Grunde genommen ein leerer Hash wie:

var newObject = {};

b. Der nächste Schritt, den JavaScript ausführt, besteht darin, alle Prototypobjekte an das neu erstellte Objekt anzuhängen

Wir haben my_person_prototypehier ähnlich wie das Prototypobjekt.

for(var key in my_person_prototype){
    newObject[key] = my_person_prototype[key];
}

Es ist nicht die Art und Weise, wie JavaScript die im Prototyp definierten Eigenschaften tatsächlich anfügt. Der tatsächliche Weg hängt mit dem Konzept der Prototypenkette zusammen.


ein. & b. Anstelle dieser beiden Schritte können Sie genau das gleiche Ergebnis erzielen, indem Sie Folgendes tun:

var newObject = Object.create(my_person_prototype);
//here you can check out the __proto__ attribute
console.log(newObject.__proto__ === my_person_prototype); //true
//and also check if you have access to your desired properties
console.log(typeof newObject.getName);//"function"

Jetzt können wir die getNameFunktion in unserem aufrufen my_person_prototype:

newObject.getName();

c. dann gibt es dieses Objekt dem Konstruktor,

Wir können dies mit unserer Probe tun wie:

Person.call(newObject, "George");

oder

Person.apply(newObject, ["George"]);

dann kann der Konstruktor tun, was er will, weil dies Innere dieses Konstruktors das Objekt ist, das gerade erstellt wurde.

Jetzt das Endergebnis, bevor die anderen Schritte simuliert werden: Objekt {Name: "George"}


Zusammenfassung:

Wenn Sie das neue Schlüsselwort für eine Funktion verwenden, rufen Sie dies grundsätzlich auf, und diese Funktion dient als Konstruktor. Wenn Sie also sagen:

new FunctionName()

JavaScript erstellt intern ein Objekt, einen leeren Hash, und gibt dieses Objekt dann an den Konstruktor weiter. Dann kann der Konstruktor tun, was er will, da dieses Innere dieses Konstruktors das gerade erstellte Objekt ist und Sie dann natürlich dieses Objekt erhalten wenn Sie die return-Anweisung in Ihrer Funktion nicht verwendet haben oder wenn Sie return undefined;am Ende Ihres Funktionskörpers eine setzen.

Wenn JavaScript eine Eigenschaft für ein Objekt nachschlägt, sucht es zunächst nach diesem Objekt. Und dann gibt es eine geheime Eigenschaft, [[prototype]]wie wir sie normalerweise haben, __proto__und diese Eigenschaft sieht JavaScript als nächstes an. Und wenn es durch das __proto__, sofern es wieder ein anderes JavaScript-Objekt ist, ein eigenes __proto__Attribut hat, geht es immer weiter, bis es den Punkt erreicht, an dem das nächste __proto__null ist. Der Punkt ist das einzige Objekt in JavaScript , dass sein __proto__Attribut null ist , ist Object.prototypeGegenstand:

console.log(Object.prototype.__proto__===null);//true

Und so funktioniert die Vererbung in JavaScript.

Die Prototypkette

Mit anderen Worten, wenn Sie eine Prototyp-Eigenschaft für eine Funktion haben und eine neue aufrufen, wird nach dem Durchsuchen des neu erstellten Objekts durch JavaScript nach Eigenschaften die Funktion überprüft, .prototypeund es ist auch möglich, dass dieses Objekt seine hat eigener interner Prototyp. und so weiter.


6
a) Bitte erklären Sie Prototypen nicht durch Kopieren von Eigenschaften. b) Das Festlegen des internen [[Prototyps]] erfolgt, bevor die Konstruktorfunktion auf die Instanz angewendet wird. Ändern Sie bitte diese Reihenfolge. c) jQuery ist in dieser Frage völlig offtopisch
Bergi

1
@Bergi: Danke für den Hinweis, ich würde mich freuen, wenn Sie mich wissen lassen, ob das jetzt in Ordnung ist.
Mehran Hatami

7
Können Sie es bitte einfach machen? Sie haben in allen Punkten Recht, aber Schüler, die diese Erklärung lesen, können zum ersten Mal wirklich verwirrt sein. Nehmen Sie ein einfacheres Beispiel und lassen Sie den Code sich selbst erklären oder fügen Sie eine Reihe von Kommentaren hinzu, um zu verdeutlichen, was Sie meinen.
PM

2
@ PM: Danke für dein Feedback. Ich habe versucht, es so einfach wie möglich zu machen, aber ich denke, Sie haben Recht, es hat noch einige vage Punkte. Also werde ich versuchen, es zu modifizieren und auch beschreibender zu sein. :)
Mehran Hatami

1
@AndreaMattioli, weil Sie auf diese Weise ein völlig neues Objekt erstellen und das alte überschreiben, das möglicherweise auch von anderen Objekten gemeinsam genutzt wird. Durch das Ersetzen __proto__werden zuerst alle Prototyp-Eigenschaften der obersten Ebene gelöscht, und dann haben Sie eine neue Protobasis, die nur dann freigegeben wird, wenn Sie sie freigeben.
Mehran Hatami

77

Die sieben Koans des Prototyps

Als Ciro San nach tiefer Meditation den Mount Fire Fox hinabstieg, war sein Geist klar und friedlich.

Seine Hand war jedoch unruhig und ergriff von sich aus einen Pinsel und notierte die folgenden Notizen.


0) Zwei verschiedene Dinge können als "Prototyp" bezeichnet werden:

  • die Prototyp-Eigenschaft, wie in obj.prototype

  • die interne Eigenschaft des Prototyps, bezeichnet wie [[Prototype]] in ES5 .

    Es kann über den ES5 abgerufen werden Object.getPrototypeOf().

    Firefox macht es über die __proto__Eigenschaft als Erweiterung zugänglich . ES6 erwähnt nun einige optionale Anforderungen für __proto__.


1) Diese Konzepte existieren, um die Frage zu beantworten:

Wenn ich das tue obj.property, wo sucht JS .property?

Intuitiv sollte die klassische Vererbung die Suche nach Eigenschaften beeinflussen.


2)

  • __proto__wird für die .Suche nach Punkteigenschaften wie in verwendet obj.property.
  • .prototypewird nicht direkt für die Suche verwendet, sondern nur indirekt, wie es __proto__bei der Objekterstellung mit bestimmt new.

Die Suchreihenfolge lautet:

  • objEigenschaften hinzugefügt mit obj.p = ...oderObject.defineProperty(obj, ...)
  • Eigentum von obj.__proto__
  • Eigentum von obj.__proto__.__proto__ und so weiter
  • Wenn dies der Fall __proto__ist null, kehren Sie zurück undefined.

Dies ist die sogenannte Prototypenkette .

Sie können das .Nachschlagen mit obj.hasOwnProperty('key')und vermeidenObject.getOwnPropertyNames(f)


3) Es gibt zwei Hauptmethoden zum Einstellen obj.__proto__:

  • new::

    var F = function() {}
    var f = new F()

    dann newhat gesetzt:

    f.__proto__ === F.prototype

    Hier wird .prototypeverwendet.

  • Object.create::

     f = Object.create(proto)

    Sätze:

    f.__proto__ === proto

4) Der Code:

var F = function(i) { this.i = i }
var f = new F(1)

Entspricht dem folgenden Diagramm (einige NumberDinge werden weggelassen):

(Function)       (  F  )                                      (f)----->(1)
 |  ^             | | ^                                        |   i    |
 |  |             | | |                                        |        |
 |  |             | | +-------------------------+              |        |
 |  |constructor  | |                           |              |        |
 |  |             | +--------------+            |              |        |
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |[[Prototype]]   |[[Prototype]]   |prototype   |constructor   |[[Prototype]]
 |  |             |                |            |              |        |
 |  |             |                |            |              |        |
 |  |             |                | +----------+              |        |
 |  |             |                | |                         |        |
 |  |             |                | | +-----------------------+        |
 |  |             |                | | |                                |
 v  |             v                v | v                                |
(Function.prototype)              (F.prototype)                         |
 |                                 |                                    |
 |                                 |                                    |
 |[[Prototype]]                    |[[Prototype]]          [[Prototype]]|
 |                                 |                                    |
 |                                 |                                    |
 | +-------------------------------+                                    |
 | |                                                                    |
 v v                                                                    v
(Object.prototype)                                       (Number.prototype)
 | | ^
 | | |
 | | +---------------------------+
 | |                             |
 | +--------------+              |
 |                |              |
 |                |              |
 |[[Prototype]]   |constructor   |prototype
 |                |              |
 |                |              |
 |                | -------------+
 |                | |
 v                v |
(null)           (Object)

Dieses Diagramm zeigt viele sprachdefinierte Objektknoten:

  • null
  • Object
  • Object.prototype
  • Function
  • Function.prototype
  • 1
  • Number.prototype(kann mit (1).__proto__Klammern gefunden werden , die zur Erfüllung der Syntax obligatorisch sind)

Unsere 2 Codezeilen haben nur die folgenden neuen Objekte erstellt:

  • f
  • F
  • F.prototype

iist jetzt eine Eigenschaft von fweil, wenn Sie tun:

var f = new F(1)

es wertet Fmit thisdem Wert ist, der new, kehrt die an wird dann zugewiesen f.


5) .constructor kommt normalerweise von F.prototypedurch die .Suche:

f.constructor === F
!f.hasOwnProperty('constructor')
Object.getPrototypeOf(f) === F.prototype
F.prototype.hasOwnProperty('constructor')
F.prototype.constructor === f.constructor

Wenn wir schreiben f.constructor, sucht JavaScript wie folgt .:

  • f hat nicht .constructor
  • f.__proto__ === F.prototypehat .constructor === F, also nimm es

Das Ergebnis f.constructor == Fist intuitiv korrekt, da Fes zum Erstellen von fz. B. gesetzten Feldern verwendet wird, ähnlich wie in klassischen OOP-Sprachen.


6) Klassische Vererbungssyntax kann durch Manipulieren von Prototypenketten erreicht werden.

ES6 fügt die Schlüsselwörter classund hinzu extends, bei denen es sich hauptsächlich um Syntaxzucker für den zuvor möglichen Wahnsinn der Prototypmanipulation handelt.

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.
c = new C(1)
c.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:
// http://stackoverflow.com/questions/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:

(c)----->(1)
 |   i
 |
 |
 |[[Prototype]]
 |
 |
 v    __proto__
(C)<--------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |[[Prototype]] 
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|[[Prototype]]    (D.prototype)--------> (inc2 function object)
| |                |             inc2
| |                |
| |                |[[Prototype]]
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)------->(inc function object)
|                inc
v
Function.prototype

Nehmen wir uns einen Moment Zeit, um zu untersuchen, wie Folgendes funktioniert:

c = new C(1)
c.inc() === 2

Die erste Zeile Sätze c.izu 1wie in „4)“.

In der zweiten Zeile, wenn wir:

c.inc()
  • .incwird durch die [[Prototype]]Kette gefunden: c-> C-> C.prototype->inc
  • Wenn wir eine Funktion in Javascript als aufrufen X.Y(), wird JavaScript innerhalb des Funktionsaufrufs automatisch thisauf gleich gesetzt !XY()

Die exakt gleiche Logik erklärt auch d.incundd.inc2 .

In diesem Artikel https://javascript.info/class#not-just-a-syntax-sugar werden weitere classwissenswerte Effekte erwähnt . Einige von ihnen sind möglicherweise nicht ohne das classSchlüsselwort erreichbar (TODO check which):


1
@ Thomasb danke! "Ich weiß nicht, woher Sie das haben": Nachdem ich einige dieser dynamischen Sprachen gesehen habe, bemerkte ich, dass es am wichtigsten an ihrem Klassensystem ist, wie die .Suche funktioniert (und wie viele Kopien der Daten erstellt werden). . Also machte ich mich daran, diesen Punkt zu verstehen. Der Rest sind Google + Blog-Beiträge + ein Js-Interpreter zur Hand. :)
Ciro Santilli 法轮功 病毒 审查 六四 六四 法轮功

1
Ich verstehe immer noch nicht, warum g.constructor === Object ist, weil Sie gesagt haben, dass "4) Wenn Sie f = new F machen, setzt new auch f.constructor = F". Könntest du mir mehr erklären? Auf jeden Fall ist dies die beste Antwort, die ich suche. Ich danke dir sehr!
nguyenngoc101

@ nguyenngoc101 danke! Das sets f.constructor = FTeil war offensichtlich falsch und widersprach weiteren Abschnitten: Wird .constructordurch .Nachschlagen in der Prototypenkette gefunden. Es wurde jetzt behoben.
Ciro Santilli 法轮功 病毒 审查 六四 事件 5

Aus der gesamten Diskussion, was ich bekomme (kam aus der klassischen Vererbung), wenn ich eine Konstruktorfunktion erstelle und versuche, eine Instanz davon mit einem neuen Operator zu erstellen, erhalte ich nur Methoden und Eigenschaften, die an das Proto-Objekt angehängt wurden, daher ist es notwendig, die gesamte Methode anzuhängen und Eigenschaften zum Proto-Objekt, wenn wir erben wollen, mi richtig?
BlackHawk

1
@CiroSantilli think 死 六四 事件 法轮功 Ich glaube nicht, dass es ein Fehler in Chromium ist. Ich denke, es ist nur ein Symptom, dass fder Prototyp Fnur zur Bauzeit erstellt wird. fwird es nicht wissen oder sich darum kümmernF.prototype zu keinem Zeitpunkt nach dem ersten Bau etwas .
John Glassmyer

76

prototypeermöglicht es Ihnen, Klassen zu machen. wenn Sie nicht verwendenprototype , wird es statisch.

Hier ist ein kurzes Beispiel.

var obj = new Object();
obj.test = function() { alert('Hello?'); };

Im obigen Fall haben Sie einen statischen Funktionsaufruftest. Auf diese Funktion kann nur von obj.test zugegriffen werden, wo Sie sich vorstellen können, dass obj eine Klasse ist.

wo wie im folgenden Code

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

Das Objekt ist zu einer Klasse geworden, die jetzt instanziiert werden kann. Es können mehrere Instanzen von obj existieren, und alle haben dietest Funktion haben.

Das Obige ist mein Verständnis. Ich mache es zu einem Community-Wiki, damit die Leute mich korrigieren können, wenn ich falsch liege.


13
-1: prototypeist eine Eigenschaft von Konstruktorfunktionen, nicht von Instanzen, dh Ihr Code ist falsch! Vielleicht meinten Sie die nicht standardmäßige Eigenschaft __proto__von Objekten, aber das ist ein ganz anderes Tier ...
Christoph

@Christoph - Danke, dass du darauf hingewiesen hast. Ich habe den Beispielcode aktualisiert.
Ramesh

3
Es steckt noch so viel mehr dahinter ... Außerdem ist JavaScript keine klassenbasierte Sprache - es befasst sich mit der Vererbung über Prototypen. Sie müssen die Unterschiede detaillierter behandeln!
James

5
Ich denke, diese Antwort ist ein wenig falsch.
Armin Cifuentes

Vielleicht ist die Antwort "irreführend", erklärt aber, wofür der Prototyp verwendet wird, und jetzt ist mir alles klar, nach all diesen "Antworten" mit Hunderten von Stimmen. Vielen Dank.
Aleks

66

Nachdem ich diesen Thread gelesen habe, bin ich verwirrt mit der JavaScript-Prototypenkette. Dann habe ich diese Diagramme gefunden

http://iwiki.readthedocs.org/en/latest/javascript/js_core.html#inheritance * [[protytype]] * und <code> prototype </ code> -Eigenschaft von Funktionsobjekten

Es ist ein übersichtliches Diagramm, das die JavaScript-Vererbung nach Prototypkette zeigt

und

http://www.javascriptbank.com/javascript/article/JavaScript_Classical_Inheritance/

Dieser enthält ein Beispiel mit Code und einige schöne Diagramme.

Die Prototypenkette greift letztendlich auf Object.prototype zurück.

Die Prototypkette kann jedes Mal technisch so lange erweitert werden, wie Sie möchten, indem der Prototyp der Unterklasse einem Objekt der übergeordneten Klasse gleichgesetzt wird.

Ich hoffe, es ist auch hilfreich für Sie, die JavaScript-Prototypenkette zu verstehen.


Ist es möglich, Javascript mehrfach zu vererben?

Ist Foo hier ein Objektliteral oder ein Funktionsobjekt? Wenn es sich um ein Objektliteral handelt, wird Foo.prototype meiner Meinung nach nicht über den Konstruktor auf Foo verweisen.
Madhur Ahuja

@ user3376708 JavaScript unterstützt nur Einzelvererbung ( Quelle )
Rafael Eyng

@ Nuno_147 Es ist zunächst nicht klar, aber wenn Sie lange genug schauen, können Sie etwas daraus machen.
Marcelocra

3
Können Sie erklären, was [[Prototype]]bedeutet?
CodyBugstein

40

Jedes Objekt hat eine interne Eigenschaft, [[Prototype]] , die es mit einem anderen Objekt verknüpft:

object [[Prototype]]  anotherObject

In herkömmlichem Javascript ist das verknüpfte Objekt die prototypeEigenschaft einer Funktion:

object [[Prototype]]  aFunction.prototype

In einigen Umgebungen wird [[Prototype]] wie folgt verfügbar gemacht __proto__:

anObject.__proto__ === anotherObject

Sie erstellen den Link [[Prototyp]] , wenn Sie ein Objekt erstellen.

// (1) Object.create:
var object = Object.create(anotherObject)
// object.__proto__ = anotherObject

// (2) ES6 object initializer:
var object = { __proto__: anotherObject };
// object.__proto__ = anotherObject

// (3) Traditional JavaScript:
var object = new aFunction;
// object.__proto__ = aFunction.prototype

Diese Aussagen sind also äquivalent:

var object = Object.create(Object.prototype);
var object = { __proto__: Object.prototype }; // ES6 only
var object = new Object;

Sie können das Linkziel nicht sehen (Object.prototype ) in einer neuen Anweisung nicht sehen. Stattdessen wird das Ziel vom Konstruktor impliziert (Object ) .

Merken:

  • Jedes Objekt hat einen Link, [[Prototyp]] , der manchmal als angezeigt wird __proto__ angezeigt wird .
  • Jede Funktion hat eine prototype Eigenschaft, die zunächst ein leeres Objekt enthält.
  • Objekte, die mit new erstellt wurden werden mit der prototypeEigenschaft ihres Konstruktors verknüpft .
  • Wenn eine Funktion niemals als Konstruktor verwendet wird, ist ihre prototype Eigenschaft nicht verwendet.
  • Wenn Sie keinen Konstruktor benötigen, verwenden Sie stattdessen Object.createnew .

1
In Revision 5 wurden einige nützliche Informationen entfernt, einschließlich Informationen zu Object.create (). Siehe Revision 4 .
Palec

@Palec was soll ich zurück hinzufügen?
Sam

2
IMO zumindest der Link zu Object.create()docs , @sam. Links zu __proto__und Object.prototypewären nette Verbesserungen. Und ich mochte Ihre Beispiele, wie Prototypen mit Konstruktoren arbeiten und Object.create(), aber sie waren wahrscheinlich der lange und weniger relevante Teil, den Sie loswerden wollten.
Palec

Aus der gesamten Diskussion, was ich bekomme (kam aus der klassischen Vererbung), wenn ich eine Konstruktorfunktion erstelle und versuche, eine Instanz davon mit einem neuen Operator zu erstellen, erhalte ich nur Methoden und Eigenschaften, die an das Proto-Objekt angehängt wurden. Daher ist es notwendig, die gesamte Methode anzuhängen und Eigenschaften zum Proto-Objekt, wenn wir erben wollen, mi richtig?
BlackHawk

29

Javascript hat keine Vererbung im üblichen Sinne, aber es hat die Prototypenkette.

Prototypkette

Wenn ein Mitglied eines Objekts im Objekt nicht gefunden werden kann, sucht es in der Prototypenkette danach. Die Kette besteht aus anderen Objekten. Auf den Prototyp einer bestimmten Instanz kann mit dem zugegriffen werden__proto__ Variablen werden. Jedes Objekt hat eines, da es in Javascript keinen Unterschied zwischen Klassen und Instanzen gibt.

Der Vorteil des Hinzufügens einer Funktion / Variablen zum Prototyp besteht darin, dass sie nicht nur für jede Instanz, sondern nur einmal im Speicher gespeichert werden muss.

Es ist auch nützlich für die Vererbung, da die Prototypkette aus vielen anderen Objekten bestehen kann.


1
FF und Chrome unterstützen Proto , aber weder IE noch Opera.
einige

Georg, bitte klären Sie für einen Noob: "Es gibt keinen Unterschied zwischen Klassen und Instanzen in Javascript." - Könnten Sie näher darauf eingehen? Wie funktioniert das?
Hamish Grubijan

Aus der gesamten Diskussion, was ich bekomme (kam aus der klassischen Vererbung), wenn ich eine Konstruktorfunktion erstelle und versuche, eine Instanz davon mit einem neuen Operator zu erstellen, erhalte ich nur Methoden und Eigenschaften, die an das Proto-Objekt angehängt wurden. Daher ist es notwendig, die gesamte Methode anzuhängen und Eigenschaften zum Proto-Objekt, wenn wir erben wollen, mi richtig?
BlackHawk

28

Dieser Artikel ist lang. Aber ich bin sicher, es wird die meisten Ihrer Fragen bezüglich der "prototypischen" Natur der JavaScript-Vererbung klären. Und noch mehr. Bitte lesen Sie den vollständigen Artikel.

JavaScript hat grundsätzlich zwei Arten von Datentypen

  • Nicht Objekte
  • Objekte

Nicht Objekte

Im Folgenden sind die Nicht-Objekt -Datentypen aufgeführt

  • Zeichenfolge
  • Nummer (einschließlich NaN und Infinity)
  • Boolesche Werte (wahr, falsch)
  • nicht definiert

Diese Datentypen werden wie folgt zurückgegeben, wenn Sie den Operator typeof verwenden

typeof "string literal" (oder eine Variable, die string literal enthält) === 'string'

typeof 5 (oder ein beliebiges numerisches Literal oder eine Variable, die ein numerisches Literal oder NaN oder Infynity enthält ) === 'number'

typeof true (oder false oder eine Variable, die true oder false enthält ) === 'boolean'

typeof undefined (oder eine undefinierte Variable oder eine Variable, die undefined enthält ) === 'undefined'

Die Datentypen string , number und boolean können sowohl als Objekte als auch als Nicht-Objekte dargestellt werden. Wenn sie als Objekte dargestellt werden, ist ihr Typ immer === 'Objekt'. Wir werden darauf zurückkommen, sobald wir die Objektdatentypen verstanden haben.

Objekte

Die Objektdatentypen können weiter in zwei Typen unterteilt werden

  1. Objekte vom Funktionstyp
  2. Objekte ohne Funktionstyp

Die Objekte vom Typ Funktion geben die Zeichenfolge 'function' mit dem Operator typeof zurück . Alle benutzerdefinierten Funktionen und alle in JavaScript integrierten Objekte, die mithilfe eines neuen Operators neue Objekte erstellen können, fallen in diese Kategorie. Zum Beispiel.

  • Objekt
  • String
  • Nummer
  • Boolescher Wert
  • Array
  • Typisierte Arrays
  • RegExp
  • Funktion
  • Alle anderen integrierten Objekte, die mithilfe des neuen Operators neue Objekte erstellen können
  • Funktion UserDefinedFunction () {/ * benutzerdefinierter Code * /}

Also, typeof (Object) === typeof (String) === typeof (Number) === typeof (Boolean) === typeof (Array) === typeof (RegExp) === typeof (Function) == = typeof (UserDefinedFunction) === 'function'

Alle Objekte vom Typ Funktion sind tatsächlich Instanzen der integrierten JavaScript- Objektfunktion (einschließlich des Funktionsobjekts , dh es ist rekursiv definiert). Es ist, als ob diese Objekte folgendermaßen definiert worden wären

var Object= new Function ([native code for object Object])
var String= new Function ([native code for object String])
var Number= new Function ([native code for object Number])
var Boolean= new Function ([native code for object Boolean])
var Array= new Function ([native code for object Array])
var RegExp= new Function ([native code for object RegExp])
var Function= new Function ([native code  for object Function])
var UserDefinedFunction= new Function ("user defined code")

Wie bereits erwähnt, können die Objekte vom Funktionstyp mit dem neuen Operator weitere Objekte erstellen . Beispielsweise kann ein Objekt vom Typ Object , String , Number , Boolean , Array , RegExp oder UserDefinedFunction mithilfe von erstellt werden

var a=new Object() or var a=Object() or var a={} //Create object of type Object
var a=new String() //Create object of type String
var a=new Number() //Create object of type Number
var a=new Boolean() //Create object of type Boolean
var a=new Array() or var a=Array() or var a=[]  //Create object of type Array
var a=new RegExp() or var a=RegExp() //Create object of type RegExp
var a=new UserDefinedFunction() 

Die Objekte so erstellt sind alle Typ Non Funktionsobjekte und zurück ihre typeof === ‚Objekt‘ . In all diesen Fällen kann das Objekt "a" mit dem Operator new keine Objekte mehr erstellen. Das Folgende ist also falsch

var b=new a() //error. a is not typeof==='function'

Das eingebaute Objekt Math ist typeof === 'object' . Daher kann ein neues Objekt vom Typ Math nicht durch einen neuen Operator erstellt werden.

var b=new Math() //error. Math is not typeof==='function'

Beachten Sie auch, dass Objekt- , Array- und RegExp- Funktionen ein neues Objekt erstellen können, ohne den Operator new zu verwenden . Die folgenden tun dies jedoch nicht.

var a=String() // Create a new Non Object string. returns a typeof==='string' 
var a=Number() // Create a new Non Object Number. returns a typeof==='number'
var a=Boolean() //Create a new Non Object Boolean. returns a typeof==='boolean'

Die benutzerdefinierten Funktionen sind Sonderfälle.

var a=UserDefinedFunction() //may or may not create an object of type UserDefinedFunction() based on how it is defined.

Da Objekte vom Typ Funktion neue Objekte erstellen können, werden sie auch als Konstruktoren bezeichnet .

Jeder Konstruktor / jede Funktion (ob eingebaut oder benutzerdefiniert) hat automatisch eine Eigenschaft namens "Prototyp", deren Wert standardmäßig als Objekt festgelegt ist. Dieses Objekt selbst hat eine Eigenschaft namens "Konstruktor", die standardmäßig auf den Konstruktor / die Funktion verweist .

Zum Beispiel, wenn wir eine Funktion definieren

function UserDefinedFunction()
{
}

Folgendes geschieht automatisch

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

Diese "Prototyp" -Eigenschaft ist nur in Objekten vom Typ "Funktion" (und niemals in Objekten vom Typ "Nicht-Funktion" ) vorhanden.

Dies liegt daran, dass beim Erstellen eines neuen Objekts (unter Verwendung eines neuen Operators) alle Eigenschaften und Methoden vom aktuellen Prototypobjekt der Konstruktorfunktion geerbt werden, dh im neu erstellten Objekt wird eine interne Referenz erstellt, die auf das Objekt verweist, auf das das aktuelle Prototypobjekt der Konstruktorfunktion verweist.

Diese "interne Referenz" , die im Objekt zum Verweisen auf geerbte Eigenschaften erstellt wird, wird als Prototyp des Objekts bezeichnet (der auf das Objekt verweist, auf das durch die Eigenschaft "Prototyp" des Konstruktors verwiesen wird, sich jedoch von diesem unterscheidet). Für jedes Objekt (Funktion oder Nichtfunktion) kann dies mit der Object.getPrototypeOf () -Methode abgerufen werden . Mit dieser Methode kann man die Prototypkette eines Objekts verfolgen.

Außerdem verfügt jedes erstellte Objekt ( Funktionstyp oder Nichtfunktionstyp ) über eine "Konstruktor" -Eigenschaft, die von dem Objekt geerbt wird, auf das durch die Prototyp-Eigenschaft der Konstruktorfunktion verwiesen wird. Standardmäßig verweist diese "Konstruktor" -Eigenschaft auf die Konstruktorfunktion , die sie erstellt hat (wenn der Standard- "Prototyp" der Konstruktorfunktion nicht geändert wird).

Für alle Objekte vom Typ Funktion ist die Konstruktorfunktion immer function Function () {}

Bei Objekten vom Typ Nichtfunktion (z. B. in Math erstelltes Javascript-Objekt) ist die Konstruktorfunktion die Funktion, die sie erstellt hat. Für das Math- Objekt ist es die Funktion Object () {} .

Alle oben erläuterten Konzepte können ohne unterstützenden Code ein wenig entmutigend zu verstehen sein. Bitte gehen Sie den folgenden Code Zeile für Zeile durch, um das Konzept zu verstehen. Versuchen Sie es auszuführen, um ein besseres Verständnis zu haben.

function UserDefinedFunction()
{ 

} 

/* creating the above function automatically does the following as mentioned earlier

UserDefinedFunction.prototype={constructor:UserDefinedFunction}

*/


var newObj_1=new UserDefinedFunction()

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays true

alert(newObj_1.constructor) //Displays function UserDefinedFunction

//Create a new property in UserDefinedFunction.prototype object

UserDefinedFunction.prototype.TestProperty="test"

alert(newObj_1.TestProperty) //Displays "test"

alert(Object.getPrototypeOf(newObj_1).TestProperty)// Displays "test"

//Create a new Object

var objA = {
        property1 : "Property1",
        constructor:Array

}


//assign a new object to UserDefinedFunction.prototype
UserDefinedFunction.prototype=objA

alert(Object.getPrototypeOf(newObj_1)===UserDefinedFunction.prototype)  //Displays false. The object referenced by UserDefinedFunction.prototype has changed

//The internal reference does not change
alert(newObj_1.constructor) // This shall still Display function UserDefinedFunction

alert(newObj_1.TestProperty) //This shall still Display "test" 

alert(Object.getPrototypeOf(newObj_1).TestProperty) //This shall still Display "test"


//Create another object of type UserDefinedFunction
var newObj_2= new UserDefinedFunction();

alert(Object.getPrototypeOf(newObj_2)===objA) //Displays true.

alert(newObj_2.constructor) //Displays function Array()

alert(newObj_2.property1) //Displays "Property1"

alert(Object.getPrototypeOf(newObj_2).property1) //Displays "Property1"

//Create a new property in objA
objA.property2="property2"

alert(objA.property2) //Displays "Property2"

alert(UserDefinedFunction.prototype.property2) //Displays "Property2"

alert(newObj_2.property2) // Displays Property2

alert(Object.getPrototypeOf(newObj_2).property2) //Displays  "Property2"

Die Prototypenkette jedes Objekts geht letztendlich auf Object.prototype zurück (das selbst kein Prototypobjekt hat). Der folgende Code kann zum Verfolgen der Prototypkette eines Objekts verwendet werden

var o=Starting object;

do {
    alert(o + "\n" + Object.getOwnPropertyNames(o))

}while(o=Object.getPrototypeOf(o))

Die Prototypenkette für verschiedene Objekte funktioniert wie folgt.

  • Jedes Funktionsobjekt (einschließlich des eingebauten Funktionsobjekts) -> Funktionsprototyp -> Objektprototyp -> null
  • Einfache Objekte (erstellt von new Object () oder {} einschließlich eingebautem Math-Objekt) -> Object.prototype -> null
  • Objekt erstellt mit new oder Object.create -> Eine oder mehrere Prototypketten -> Object.prototype -> null

Verwenden Sie zum Erstellen eines Objekts ohne Prototyp Folgendes:

var o=Object.create(null)
alert(Object.getPrototypeOf(o)) //Displays null

Man könnte denken, dass das Setzen der Prototyp-Eigenschaft des Konstruktors auf Null ein Objekt mit einem Null-Prototyp erzeugen soll. In solchen Fällen wird der Prototyp des neu erstellten Objekts jedoch auf Object.prototype und sein Konstruktor auf die Funktion Object gesetzt. Dies wird durch den folgenden Code demonstriert

function UserDefinedFunction(){}
UserDefinedFunction.prototype=null// Can be set to any non object value (number,string,undefined etc.)

var o=new UserDefinedFunction()
alert(Object.getPrototypeOf(o)==Object.prototype)   //Displays true
alert(o.constructor)    //Displays Function Object

Folgen Sie in der Zusammenfassung dieses Artikels

  • Es gibt zwei Arten von Objekten: Funktionstypen und Nichtfunktionstypen
  • Nur Objekte vom Typ Funktion können mit dem Operator new ein neues Objekt erstellen . Die so erstellten Objekte sind Objekte vom Typ Nichtfunktion. Die Objekte vom Typ Non Function können mit dem Operator new kein Objekt mehr erstellen .

  • Alle Objekte vom Typ Funktion haben standardmäßig die Eigenschaft "Prototyp" . Diese "Prototyp" -Eigenschaft verweist auf ein Objekt mit einer "Konstruktor" -Eigenschaft, die standardmäßig auf das Objekt vom Funktionstyp selbst verweist .

  • Alle Objekte ( Funktionstyp und Nichtfunktionstyp ) haben eine "Konstruktor" -Eigenschaft, die standardmäßig auf den Funktionstyp Objekt / verweist Konstruktor sie erstellt wurden.

  • Jedes Objekt, das intern erstellt wird, verweist auf das Objekt, auf das durch die Eigenschaft "prototype" des Konstruktors verwiesen wird , der es erstellt hat. Dieses Objekt wird als Prototyp des erstellten Objekts bezeichnet (unterscheidet sich von der Eigenschaft "Prototyp" von Funktionstypobjekten, auf die es verweist). Auf diese Weise kann das erstellte Objekt direkt auf die Methoden und Eigenschaften zugreifen, die in dem Objekt definiert sind, auf das die Eigenschaft "Prototyp" des Konstruktors verweist (zum Zeitpunkt der Objekterstellung).

  • Der Prototyp eines Objekts (und damit seine geerbten Eigenschaftsnamen) kann mit der Object.getPrototypeOf () -Methode abgerufen werden . Tatsächlich kann diese Methode zum Navigieren in der gesamten Prototypenkette des Objekts verwendet werden.

  • Die Prototypkette jedes Objekts geht letztendlich auf Object.prototype zurück (es sei denn, das Objekt wird mit Object.create (null) erstellt. In diesem Fall hat das Objekt keinen Prototyp).

  • typeof (new Array ()) === 'object' ist ein Sprachentwurf und kein Fehler, wie von Douglas Crockford angegeben

  • Wenn Sie die Prototyp-Eigenschaft des Konstruktors auf null (oder undefiniert, number, true, false, string) setzen, wird kein Objekt mit einem Null-Prototyp erstellt. In solchen Fällen wird der Prototyp des neu erstellten Objekts auf Object.prototype und der Konstruktor auf die Funktion Object gesetzt.

Hoffe das hilft.


24

Das Konzept der prototypalVererbung ist für viele Entwickler eines der kompliziertesten. Versuchen wir, die Wurzel des Problems zu verstehen, um es prototypal inheritancebesser zu verstehen . Beginnen wir mit einer plainFunktion.

Geben Sie hier die Bildbeschreibung ein

Wenn wir einen newOperator auf dem verwenden Tree function, rufen wir ihn als constructorFunktion auf.

Geben Sie hier die Bildbeschreibung ein

Jede JavaScriptFunktion hat eine prototype. Wenn Sie das protokollieren Tree.prototype, erhalten Sie ...

Geben Sie hier die Bildbeschreibung ein

Wenn Sie sich die obige console.log()Ausgabe ansehen , sehen Sie möglicherweise eine Konstruktoreigenschaft Tree.prototypeund auch eine __proto__Eigenschaft. Das __proto__stellt das dar prototype, worauf dies functionbasiert, und da dies nur eine Ebene ist , die noch JavaScript functionnicht inheritanceeingerichtet ist, bezieht es sich auf das Object prototype, was gerade in JavaScript eingebaut wurde ...

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

Das hat Dinge wie .toString, .toValue, .hasOwnPropertyetc ...

__proto__was gebracht wurde meine Mozilla ist veraltet und wird durch Object.getPrototypeOfMethode ersetzt, um die zu bekommen object's prototype.

Geben Sie hier die Bildbeschreibung ein

Object.getPrototypeOf(Tree.prototype); // Object {} 

Fügen wir unserer Methode eine Methode hinzu Tree prototype.

Geben Sie hier die Bildbeschreibung ein

Wir haben das geändert Rootund einen functionZweig hinzugefügt .

Geben Sie hier die Bildbeschreibung ein

Das heißt, wenn Sie ein instancevon erstellen Tree, können Sie dessen branchMethode aufrufen .

Geben Sie hier die Bildbeschreibung ein

Wir können auch hinzufügen primitivesoder objectszu unserem Prototype.

Geben Sie hier die Bildbeschreibung ein

Fügen wir ein child-treezu unserem hinzu Tree.

Geben Sie hier die Bildbeschreibung ein

Hier Childerbt das prototypevon Tree. Wir verwenden hier eine Object.create()Methode, um ein neues Objekt zu erstellen, das auf dem basiert, was Sie übergeben. Hier ist es Tree.prototype. In diesem Fall setzen wir den Prototyp von Child auf ein neues Objekt, das mit dem TreePrototyp identisch aussieht . Als nächstes setzen wir das Child's constructor to Child, wenn wir es nicht tun, würde es darauf hinweisen Tree().

Geben Sie hier die Bildbeschreibung ein

ChildJetzt hat es seine eigenen prototype, seine __proto__Punkte auf Treeund Tree's prototypePunkte auf Basis Object.

Child  
|
 \
  \
   Tree.prototype
   - branch
   |
   |
    \
     \
      Object.prototype
      -toString
      -valueOf
      -etc., etc.

Jetzt erstellen Sie ein instancevon Childund rufen auf, branchdas ursprünglich in verfügbar war Tree. Wir haben unsere branchauf der nicht wirklich definiert Child prototype. ABER, in dem Root prototypedas Kind erbt.

Geben Sie hier die Bildbeschreibung ein

In JS ist alles kein Objekt, alles kann sich wie ein Objekt verhalten.

Javascripthat Primitive wie strings, number, booleans, undefined, null.Sie sind nicht object(i.e reference types), können aber sicherlich wie ein handeln object. Schauen wir uns hier ein Beispiel an.

Geben Sie hier die Bildbeschreibung ein

In der ersten Zeile dieser Liste primitivewird dem Namen ein Zeichenfolgenwert zugewiesen. In der zweiten Zeile wird der Name wie ein behandelt objectund charAt(0)mit der Punktnotation aufgerufen.

Dies passiert hinter den Kulissen: // was die JavaScriptEngine tut

Geben Sie hier die Bildbeschreibung ein

Das String objectexistiert nur für eine Anweisung, bevor es zerstört wird (ein Prozess, der aufgerufen wird autoboxing). Kommen wir noch einmal zu unserem zurück prototypal inheritance.

  • Javascriptunterstützt die Vererbung über delegationbasierend auf prototypes.
  • Jeder Functionhat eine prototypeEigenschaft, die auf ein anderes Objekt verweist.
  • properties/functionswerden von objectsich aus oder über prototypeKette gesucht , wenn es nicht existiert

A prototypein JS ist ein Objekt, das yieldsSie dem Elternteil eines anderen übermitteln object. [dh Delegation] Delegation bedeutet, dass Sie, wenn Sie nicht in der Lage sind, etwas zu tun, jemand anderem sagen, dass er es für Sie tun soll.

Geben Sie hier die Bildbeschreibung ein

https://jsfiddle.net/say0tzpL/1/

Wenn Sie die obige Geige nachschlagen, hat der Hund Zugriff auf die toStringMethode, die jedoch nicht darin verfügbar ist, sondern über die Prototypenkette, an die delegiert wirdObject.prototype

Geben Sie hier die Bildbeschreibung ein

Wenn Sie sich das folgende ansehen, versuchen wir, auf die callMethode zuzugreifen , die in jedem verfügbar ist function.

Geben Sie hier die Bildbeschreibung ein

https://jsfiddle.net/rknffckc/

Wenn Sie die obige Geige nachschlagen, hat ProfileFunction Zugriff auf die callMethode, diese ist jedoch nicht verfügbar, sondern über die Prototypenkette, an die delegiert wirdFunction.prototype

Geben Sie hier die Bildbeschreibung ein

Hinweis: prototype ist eine Eigenschaft des Funktionskonstruktors, während __proto__eine Eigenschaft der aus dem Funktionskonstruktor erstellten Objekte ist. Jede Funktion enthält eine prototypeEigenschaft, deren Wert leer ist object. Wenn wir eine Instanz der Funktion erstellen, erhalten wir eine interne Eigenschaft [[Prototype]]oder __proto__deren Referenz der Prototyp der Funktion ist constructor.

Geben Sie hier die Bildbeschreibung ein

Das obige Diagramm sieht etwas kompliziert aus, zeigt aber das ganze Bild, wie prototype chaining funktioniert. Lassen Sie uns dies langsam durchgehen:

Es gibt zwei Instanzen b1und b2, deren Konstruktor Barund übergeordnetes Element Foo ist und zwei Methoden aus der Prototypenkette identifyund speakvia Barund hatFoo

Geben Sie hier die Bildbeschreibung ein

https://jsfiddle.net/kbp7jr7n/

Wenn Sie den obigen Code nachschlagen, haben wir einen FooKonstruktor mit der Methode identify()und einen BarKonstruktor mit der speakMethode. Wir schaffen zwei BarInstanzen b1und b2dessen Muttertypen ist Foo. Während wir die speakMethode von aufrufen Bar, können wir nun identifizieren, wer über die prototypeKette das Sprechen aufruft .

Geben Sie hier die Bildbeschreibung ein

Barhat jetzt alle Methoden, von Foodenen in seiner definiert sind prototype. Lassen Sie uns weiter gehen, um das Object.prototypeund Function.prototypeund ihre Beziehung zu verstehen . Wenn Sie den Konstruktor nachschlagen Foo, Barund Objectsind Function constructor.

Geben Sie hier die Bildbeschreibung ein

Das prototypevon Barist Foo, prototypevon Fooist Objectund wenn Sie genau hinschauen, ist das prototypevon Fooverwandt mit Object.prototype.

Geben Sie hier die Bildbeschreibung ein

Bevor wir dies schließen, lassen Sie uns hier nur einen kleinen Code einschließen, um alles oben zusammenzufassen . Wir verwenden instanceofhier den Operator, um zu überprüfen, ob ein objectin seiner prototypeKette die prototypeEigenschaft eines hat, constructordie das gesamte große Diagramm zusammenfasst.

Geben Sie hier die Bildbeschreibung ein

Ich hoffe, dass dieses Add einige Informationen enthält. Ich weiß, dass dies ein bisschen zu verstehen sein könnte. In einfachen Worten, es sind nur Objekte, die mit Objekten verbunden sind.


22

Was ist der genaue Zweck dieser ".prototype" -Eigenschaft?

Die Schnittstelle zu Standardklassen wird erweiterbar. Beispielsweise verwenden Sie die ArrayKlasse und müssen außerdem einen benutzerdefinierten Serializer für alle Ihre Array-Objekte hinzufügen. Würden Sie Zeit damit verbringen, eine Unterklasse zu codieren oder Komposition oder ... zu verwenden? Die Prototyp-Eigenschaft löst dieses Problem, indem die Benutzer die genaue Gruppe von Mitgliedern / Methoden steuern können, die einer Klasse zur Verfügung stehen.

Stellen Sie sich Prototypen als zusätzlichen vtable-Zeiger vor. Wenn einige Mitglieder in der ursprünglichen Klasse fehlen, wird der Prototyp zur Laufzeit nachgeschlagen.


21

Es kann hilfreich sein, Prototypketten in zwei Kategorien einzuteilen.

Betrachten Sie den Konstruktor:

 function Person() {}

Der Wert von Object.getPrototypeOf(Person)ist eine Funktion. In der Tat ist es Function.prototype. Da Persones als Funktion erstellt wurde, verwendet es dasselbe Prototyp-Funktionsobjekt wie alle Funktionen. Es ist dasselbe wie Person.__proto__, aber diese Eigenschaft sollte nicht verwendet werden. Wie auch immer, mit Object.getPrototypeOf(Person)Ihnen steigen Sie effektiv die Leiter der sogenannten Prototypenkette hinauf.

Die Kette nach oben sieht folgendermaßen aus:

    PersonFunction.prototypeObject.prototype(Endpunkt)

Wichtig ist, dass diese Prototypenkette wenig mit den Objekten zu tun hat, Persondie konstruiert werden können . Diese konstruierten Objekte haben eine eigene Prototypkette, und diese Kette kann möglicherweise keinen engen Vorfahren gemeinsam mit dem oben genannten haben.

Nehmen Sie zum Beispiel dieses Objekt:

var p = new Person();

p hat keine direkte Beziehung zwischen Prototyp und Kette zur Person . Ihre Beziehung ist eine andere. Das Objekt p hat eine eigene Prototypkette. Wenn Object.getPrototypeOfSie verwenden, finden Sie die Kette wie folgt:

    pPerson.prototypeObject.prototype(Endpunkt)

Es gibt kein Funktionsobjekt in dieser Kette (obwohl das sein könnte).

Es Personscheint also mit zwei Arten von Ketten verwandt zu sein, die ihr eigenes Leben führen. Um von einer Kette zur anderen zu "springen", verwenden Sie:

  1. .prototype: Springe von der Konstruktorkette zur Kette des erstellten Objekts. Diese Eigenschaft ist daher nur für Funktionsobjekte definiert (wie newsie nur für Funktionen verwendet werden können).

  2. .constructor: Springe von der Kette des erstellten Objekts zur Kette des Konstruktors.

Hier ist eine visuelle Darstellung der beiden beteiligten Prototypketten, dargestellt als Spalten:

Geben Sie hier die Bildbeschreibung ein

Zusammenfassen:

Die prototypeEigenschaft enthält keine Informationen zur Prototypenkette des Subjekts , sondern zu Objekten, die vom Subjekt erstellt wurden.

Es ist keine Überraschung, dass der Name der Immobilie prototypezu Verwirrung führen kann. Es wäre vielleicht klarer gewesen, wenn diese Eigenschaft benannt worden wäre prototypeOfConstructedInstancesoder etwas in dieser Richtung.

Sie können zwischen den beiden Prototypenketten hin und her springen:

Person.prototype.constructor === Person

Diese Symmetrie kann durch explizites Zuweisen eines anderen Objekts zur prototypeEigenschaft aufgehoben werden (dazu später mehr).

Erstellen Sie eine Funktion, erhalten Sie zwei Objekte

Person.prototypeist ein Objekt, das zur gleichen Zeit erstellt wurde, als die Funktion Personerstellt wurde. Es hat Personals Konstruktor, obwohl dieser Konstruktor noch nicht ausgeführt wurde. Es werden also zwei Objekte gleichzeitig erstellt:

  1. Die Funktion Personselbst
  2. Das Objekt, das als Prototyp fungiert, wenn die Funktion als Konstruktor aufgerufen wird

Beide sind Objekte, haben jedoch unterschiedliche Rollen: Das Funktionsobjekt wird konstruiert , während das andere Objekt den Prototyp eines Objekts darstellt, das von dieser Funktion erstellt wird. Das Prototypobjekt wird zum übergeordneten Objekt des konstruierten Objekts in seiner Prototypenkette.

Da eine Funktion auch ein Objekt ist, hat sie auch ein eigenes übergeordnetes Element in ihrer eigenen Prototypkette. Denken Sie jedoch daran, dass es bei diesen beiden Ketten um unterschiedliche Dinge geht.

Hier sind einige Gleichungen, die helfen könnten, das Problem zu verstehen - all diese Drucke true:

function Person() {};

// This is prototype chain info for the constructor (the function object):
console.log(Object.getPrototypeOf(Person) === Function.prototype);
// Step further up in the same hierarchy:
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype);
console.log(Object.getPrototypeOf(Object.prototype) === null);
console.log(Person.__proto__ === Function.prototype);
// Here we swap lanes, and look at the constructor of the constructor
console.log(Person.constructor === Function);
console.log(Person instanceof Function);

// Person.prototype was created by Person (at the time of its creation)
// Here we swap lanes back and forth:
console.log(Person.prototype.constructor === Person);
// Although it is not an instance of it:
console.log(!(Person.prototype instanceof Person));
// Instances are objects created by the constructor:
var p = new Person();
// Similarly to what was shown for the constructor, here we have
// the same for the object created by the constructor:
console.log(Object.getPrototypeOf(p) === Person.prototype);
console.log(p.__proto__ === Person.prototype);
// Here we swap lanes, and look at the constructor
console.log(p.constructor === Person);
console.log(p instanceof Person);

Hinzufügen von Ebenen zur Prototypenkette

Obwohl beim Erstellen einer Konstruktorfunktion ein Prototypobjekt erstellt wird, können Sie dieses Objekt ignorieren und ein anderes Objekt zuweisen, das als Prototyp für alle nachfolgenden Instanzen verwendet werden soll, die von diesem Konstruktor erstellt wurden.

Zum Beispiel:

function Thief() { }
var p = new Person();
Thief.prototype = p; // this determines the prototype for any new Thief objects:
var t = new Thief();

Jetzt ist die Prototypkette von t einen Schritt länger als die von p :

    tpPerson.prototypeObject.prototype(Endpunkt)

Die andere Prototypenkette ist nicht länger: Thiefund Personsind Geschwister, die denselben Elternteil in ihrer Prototypenkette teilen:

    Person}
    Thief  } → Function.prototypeObject.prototype(Endpunkt)

Die zuvor präsentierte Grafik kann dann darauf erweitert werden (das Original Thief.prototypewird weggelassen):

Geben Sie hier die Bildbeschreibung ein

Die blauen Linien repräsentieren Prototypketten, die anderen farbigen Linien repräsentieren andere Beziehungen:

  • zwischen einem Objekt und seinem Konstruktor
  • zwischen einem Konstruktor und dem Prototypobjekt, das zum Erstellen von Objekten verwendet wird


16

Ich fand es hilfreich, die "Prototypenkette" als rekursive Konvention zu erklären, wenn obj_n.prop_Xdarauf verwiesen wird:

Wenn obj_n.prop_Xnicht vorhanden, überprüfen Sie, obj_n+1.prop_Xwoobj_n+1 = obj_n.[[prototype]]

Wenn das prop_Xschließlich im k-ten Prototypobjekt gefunden wird, dann

obj_1.prop_X = obj_1.[[prototype]].[[prototype]]..(k-times)..[[prototype]].prop_X

Ein Diagramm der Beziehung von Javascript-Objekten anhand ihrer Eigenschaften finden Sie hier:

js Objektdiagramm

http://jsobjects.org


14

Wenn ein Konstruktor ein Objekt erstellt, verweist dieses Objekt implizit auf die Eigenschaft "Prototyp" des Konstruktors, um Eigenschaftsreferenzen aufzulösen. Auf die Eigenschaft "Prototyp" des Konstruktors kann durch den Programmausdruck constructor.prototype verwiesen werden, und Eigenschaften, die dem Prototyp eines Objekts hinzugefügt wurden, werden durch Vererbung von allen Objekten gemeinsam genutzt, die den Prototyp gemeinsam nutzen.


11

Hier gibt es zwei verschiedene, aber verwandte Einheiten, die erklärt werden müssen:

  • Die .prototypeEigenschaft von Funktionen.
  • Die [[Prototype]][1] -Eigenschaft aller Objekte [2] .

Dies sind zwei verschiedene Dinge.

Die [[Prototype]]Eigenschaft:

Dies ist eine Eigenschaft, die für alle [2] Objekte vorhanden ist.

Was hier gespeichert ist, ist ein anderes Objekt, das als Objekt selbst ein [[Prototype]]eigenes hat, das auf ein anderes Objekt zeigt. Das andere Objekt hat ein [[Prototype]]eigenes. Diese Geschichte wird fortgesetzt, bis Sie das prototypische Objekt erreichen, das Methoden bereitstellt, auf die für alle Objekte (wie .toString) zugegriffen werden kann .

Die [[Prototype]]Eigenschaft ist Teil dessen, was die [[Prototype]]Kette bildet . Diese Kette von [[Prototype]]Objekten ist , was untersucht wird , wenn zum Beispiel [[Get]]oder [[Set]]Operationen an einem Objekt ausgeführt werden:

var obj = {}
obj.a         // [[Get]] consults prototype chain
obj.b = 20    // [[Set]] consults prototype chain

Die .prototypeEigenschaft:

Dies ist eine Eigenschaft, die nur für Funktionen vorhanden ist. Mit einer sehr einfachen Funktion:

function Bar(){};

Die .prototypeEigenschaft enthält ein Objekt , das Ihnen zugewiesen wird, b.[[Prototype]]wenn Sie dies tun var b = new Bar. Sie können dies leicht untersuchen:

// Both assign Bar.prototype to b1/b2[[Prototype]]
var b = new Bar;
// Object.getPrototypeOf grabs the objects [[Prototype]]
console.log(Object.getPrototypeOf(b) === Bar.prototype) // true

Eines der wichtigsten .prototypeist das der ObjectFunktion . Dieser Prototyp enthält das prototypische Objekt, das alle [[Prototype]]Ketten enthalten. Darauf werden alle verfügbaren Methoden für neue Objekte definiert:

// Get properties that are defined on this object
console.log(Object.getOwnPropertyDescriptors(Object.prototype))

Da .prototypees sich nun um ein Objekt handelt, hat es eine [[Prototype]]Eigenschaft. Wenn Sie machen keine Zuweisungen an Function.prototype, die .prototype‚s [[Prototype]]verweist auf die prototypische Objekt (Object.prototype ). Dies wird automatisch durchgeführt, wenn Sie eine neue Funktion erstellen.

Auf diese Weise erhalten Sie jedes Mal, wenn Sie new Bar;die Prototypenkette für Sie einrichten, alles definiert Bar.prototypeund alles definiert auf Object.prototype:

var b = new Bar;
// Get all Bar.prototype properties
console.log(b.__proto__ === Bar.prototype)
// Get all Object.prototype properties
console.log(b.__proto__.__proto__ === Object.prototype)

Wenn Sie tun make Zuweisungen Function.prototypealles , was Sie tun , ist die Prototypkette erstreckt , ein weiteres Objekt umfassen. Es ist wie eine Einfügung in eine einfach verknüpfte Liste.

Dies ändert im Wesentlichen die [[Prototype]]Kette, sodass Eigenschaften, die für das zugewiesene Objekt definiert Function.prototypesind, von jedem von der Funktion erstellten Objekt gesehen werden können.


[1: Das wird niemanden verwirren; in vielen Implementierungen über die __proto__Eigenschaft verfügbar gemacht .
[2]: Alle außer null.


10

Lassen Sie mich Ihnen mein Verständnis von Prototypen erklären. Ich werde das Erbe hier nicht mit anderen Sprachen vergleichen. Ich wünschte, die Leute würden aufhören, Sprachen zu vergleichen, und die Sprache einfach als sich selbst verstehen. Das Verständnis von Prototypen und prototypischer Vererbung ist so einfach, wie ich Ihnen weiter unten zeigen werde.

Der Prototyp ist wie ein Modell, auf dessen Grundlage Sie ein Produkt erstellen. Der entscheidende Punkt, den Sie verstehen müssen, ist, dass die Verbindung zwischen dem Prototyp und dem Produkt dauerhaft ist, wenn Sie ein Objekt mit einem anderen Objekt als Prototyp erstellen. Zum Beispiel:

var model = {x:2};
var product = Object.create(model);
model.y = 5;
product.y
=>5

Jedes Objekt enthält eine interne Eigenschaft namens [[Prototyp]], auf die die Object.getPrototypeOf()Funktion zugreifen kann. Object.create(model)erstellt ein neues Objekt und setzt es der [[Prototyp]] Eigenschaft für das Objekt - Modell . Daher , wenn Sie das tun Object.getPrototypeOf(product), werden Sie das Objekt erhalten Modell .

Die Eigenschaften des Produkts werden folgendermaßen behandelt:

  • Wenn auf eine Eigenschaft zugegriffen wird, um nur ihren Wert zu lesen, wird sie in der Bereichskette nachgeschlagen. Die Suche nach der Variablen beginnt vom Produkt bis zum Prototyp. Wenn eine solche Variable in der Suche gefunden wird, wird die Suche genau dort gestoppt und der Wert zurückgegeben. Wenn eine solche Variable nicht in der Bereichskette gefunden werden kann, wird undefined zurückgegeben.
  • Wenn eine Eigenschaft geschrieben (geändert) wird, wird die Eigenschaft immer auf das Produktobjekt geschrieben . Wenn das Produkt noch keine solche Eigenschaft hat, wird es implizit erstellt und geschrieben.

Eine solche Verknüpfung von Objekten mithilfe der Prototyp-Eigenschaft wird als prototypische Vererbung bezeichnet. Da ist es so einfach, stimme zu?


Nicht immer auf das Produkt im Auftrag geschrieben. Sie machen nicht sehr deutlich, dass instanzspezifische Mitglieder initialisiert werden müssen und freigegebene Mitglieder den Prototyp verwenden können. Besonders wenn Sie instanzspezifische
HMR

HMR: In Ihrem Beispiel in Ihrer Antwort der ben.food.push ("Hamburger"); line ändert die Eigenschaft des Prototypobjekts aus folgenden Gründen: 1.) Zuerst wird ben.food nachgeschlagen, und jede Suchaktion sucht einfach nach der Bereichskette. 2.) Die Push-Funktion dieses ben.food-Objekts wird ausgeführt. Mit Schreibmodus in meiner Antwort meine ich, wenn Sie explizit einen Wert festlegen, wie in: ben.food = ['Idly']; Dadurch wird immer eine neue Eigenschaft (falls nicht bereits vorhanden) für das Produktobjekt erstellt und anschließend der Wert zugewiesen.
Aravind

HMR: Vielen Dank für Ihren Kommentar, der mich zum Nachdenken und Testen meines Verständnisses gebracht hat.
Aravind

Wenn Sie ben.food neu zuweisen, wird das Lebensmittelelement beschattet, es sei denn, Lebensmittel werden mit Object.defineProperty, Object.defineProperties oder Object.create mit dem zweiten Argument erstellt (also nicht immer). Sie können den Prototyp sogar mit (wie es aussieht) einer Neuzuweisung ändern, wenn Sie einen Getter-Setter erstellt haben. Wenn es um Vererbungsmuster geht, verstehe ich, dass die Konstruktorfunktion schwer zu verstehen ist und einige Hauptprobleme hat, aber es ist gut, wenn Sie sie verstehen. Die Vererbung in JavaScript beginnt und endet nicht mit dem Festlegen eines Prototyps. Initialisierungen (Konstruktoren) sollen ebenfalls (erneut) verwendet werden.
HMR

Ihre Antwort ist gut für die Erklärung des Prototyps, kann jedoch durch eine zu starke Vereinfachung der Vererbung in JavaScript und instanzspezifischen Elementen falsch interpretiert werden. Es wurden viele Fragen gestellt, warum sich das Mutieren eines Prototypmitglieds auf einer Instanz auf andere Instanzen auswirkt.
HMR


10

Betrachten Sie das folgende keyValueStoreObjekt:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
        this.get = function(key) { return this.data[key]; };
        this.set = function(key, value) { this.data[key] = value; };
        this.delete = function(key) { delete this.data[key]; };
        this.getLength = function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  { // Singleton public properties
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Ich kann eine neue Instanz dieses Objekts erstellen, indem ich Folgendes tue:

kvs = keyValueStore.create();

Jede Instanz dieses Objekts hätte die folgenden öffentlichen Eigenschaften:

  • data
  • get
  • set
  • delete
  • getLength

Angenommen, wir erstellen 100 Instanzen dieses keyValueStoreObjekts. Auch wenn get, set, delete, getLengthwird für jede dieser 100 Fälle genau die gleiche Sache zu tun hat jede Instanz eine eigene Kopie dieser Funktion.

Nun stellen Sie, wenn Sie nur einen einzigen haben könnten get, set, deleteund zu getLengthkopieren, und jede Instanz würde die gleiche Funktion verweisen. Dies wäre besser für die Leistung und würde weniger Speicher benötigen.

Hier kommen Prototypen ins Spiel. Ein Prototyp ist eine "Blaupause" von Eigenschaften, die geerbt, aber nicht von Instanzen kopiert werden. Dies bedeutet, dass es für alle Instanzen eines Objekts nur einmal im Speicher vorhanden ist und von allen diesen Instanzen gemeinsam genutzt wird.

Betrachten Sie nun das keyValueStoreObjekt erneut. Ich könnte es so umschreiben:

var keyValueStore = (function() {
    var count = 0;
    var kvs = function() {
        count++;
        this.data = {};
    };

    kvs.prototype = {
        'get' : function(key) { return this.data[key]; },
        'set' : function(key, value) { this.data[key] = value; },
        'delete' : function(key) { delete this.data[key]; },
        'getLength' : function() {
            var l = 0;
            for (p in this.data) l++;
            return l;
        }
    };

    return  {
        'create' : function() { return new kvs(); },
        'count' : function() { return count; }
    };
})();

Dies entspricht genau der vorherigen Version des keyValueStoreObjekts, außer dass alle seine Methoden jetzt in einem Prototyp gespeichert sind. Dies bedeutet, dass alle 100 Instanzen jetzt diese vier Methoden gemeinsam nutzen, anstatt jeweils eine eigene Kopie zu haben.


9

Zusammenfassung:

  • Funktionen sind Objekte in Javascript und können daher Eigenschaften haben
  • (Konstruktor-) Funktionen haben immer eine Prototyp-Eigenschaft
  • Wenn eine Funktion als Konstruktor mit dem newSchlüsselwort verwendet wird, erhält das Objekt einen Prototyp. Ein Verweis auf diesen Prototyp befindet sich in der __proto__Eigenschaft des neu erstellten Objekts.
  • Diese __proto__Eigenschaft bezieht sich auf die prototypeEigenschaft der Konstruktorfunktion.

Beispiel:

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

let me = new Person('willem');

console.log(Person.prototype) // Person has a prototype property

console.log(Person.prototype === me.__proto__) // the __proto__ property of the instance refers to prototype property of the function.

Warum ist das nützlich:

Javascript verfügt über einen Mechanismus zum Nachschlagen von Eigenschaften für Objekte, der als "prototypische Vererbung" bezeichnet wird.

  • Zunächst wird geprüft, ob sich die Eigenschaft auf dem Objekt selbst befindet. In diesem Fall wird diese Eigenschaft zurückgegeben.
  • Befindet sich das Grundstück nicht auf dem Objekt selbst, klettert es auf die Protochain. Grundsätzlich wird das Objekt betrachtet, auf das sich die Proto- Eigenschaft bezieht . Dort wird geprüft, ob die Eigenschaft für das von proto referenzierte Objekt verfügbar ist
  • Wenn sich die Eigenschaft nicht auf dem Proto- Objekt befindet, klettert sie die Proto- Kette bis zum Objekt-Objekt hinauf.
  • Wenn die Eigenschaft nirgendwo auf dem Objekt und seiner Prototypkette gefunden werden kann, wird sie undefiniert zurückgegeben.

Zum Beispiel:

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

let mySelf = new Person('Willem');

console.log(mySelf.__proto__ === Person.prototype);

console.log(mySelf.__proto__.__proto__ === Object.prototype);

Aktualisieren:

Die __proto__Eigenschaft ist veraltet, obwohl sie in den meisten modernen Browsern implementiert ist. Ein besserer Weg, um die Prototyp-Objektreferenz zu erhalten, wäre:

Object.getPrototypeOf()


7

Ich mag immer Analogien, wenn es darum geht, solche Dinge zu verstehen. "Prototypische Vererbung" ist meiner Meinung nach im Vergleich zur Klassenbass-Vererbung ziemlich verwirrend, obwohl Prototypen ein viel einfacheres Paradigma sind. Tatsächlich gibt es bei Prototypen wirklich keine Vererbung, daher ist der Name an und für sich irreführend, es ist eher eine Art "Delegation".

Stell dir das vor ....

Du bist in der High School und du bist in der Klasse und hast ein Quiz, das heute fällig ist, aber du hast keinen Stift, um deine Antworten auszufüllen. Doh!

Sie sitzen neben Ihrem Freund Finnius, der vielleicht einen Stift hat. Sie fragen, und er sieht sich erfolglos an seinem Schreibtisch um, aber anstatt zu sagen "Ich habe keinen Stift", ist er ein netter Freund, den er mit seinem anderen Freund Derp überprüft, ob er einen Stift hat. Derp hat in der Tat einen Ersatzstift und gibt ihn an Finnius zurück, der ihn an Sie weitergibt, um Ihr Quiz abzuschließen. Derp hat den Stift Finnius anvertraut, der den Stift zur Verwendung an Sie delegiert hat.

Wichtig ist hier, dass Derp Ihnen den Stift nicht gibt, da Sie keine direkte Beziehung zu ihm haben.

Dies ist ein vereinfachtes Beispiel für die Funktionsweise von Prototypen, bei dem ein Datenbaum nach dem gesuchten Objekt durchsucht wird.


3

ein weiteres Schema, das __proto__- , Prototyp- und Konstruktorbeziehungen zeigt : Geben Sie hier die Bildbeschreibung ein


1

Es ist nur so, dass Sie bereits ein Objekt mit haben, Object.newaber Sie haben immer noch kein Objekt, wenn Sie die Konstruktorsyntax verwenden.


1

Es ist wichtig zu verstehen, dass zwischen dem Prototyp eines Objekts (der über Object.getPrototypeOf(obj)oder über die veraltete __proto__Eigenschaft verfügbar ist ) und der prototypeEigenschaft für Konstruktorfunktionen unterschieden wird. Ersteres ist die Eigenschaft für jede Instanz, und letzteres ist die Eigenschaft für den Konstruktor. Das heißt, Object.getPrototypeOf(new Foobar())bezieht sich auf das gleiche Objekt wie Foobar.prototype.

Referenz: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes


0

Der Prototyp erstellt ein neues Objekt, indem er ein vorhandenes Objekt klont . Wenn wir also wirklich an Prototypen denken, können wir wirklich daran denken, etwas zu klonen oder zu kopieren, anstatt es zu erfinden.

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.