Die Eigenschaft "prototype"
wird häufig vom Kern von JavaScript selbst verwendet. Alle integrierten Konstruktorfunktionen verwenden es.
Zuerst schauen wir uns die Details an und dann, wie man damit neue Funktionen zu integrierten Objekten hinzufügt.
Nehmen wir an, wir geben ein leeres Objekt aus:
sei obj = {}; alarm( obj ); // "[Objekt Objekt]" ?
Wo ist der Code, der die Zeichenfolge "[object Object]"
generiert? Das ist eine integrierte toString
Methode, aber wo ist sie? Das obj
ist leer!
…Aber die Kurzschreibweise obj = {}
ist dieselbe wie obj = new Object()
, wobei Object
eine integrierte Objektkonstruktorfunktion mit einem eigenen prototype
ist, der mit toString
und anderen Methoden auf ein großes Objekt verweist.
Folgendes ist los:
Wenn new Object()
aufgerufen wird (oder ein Literalobjekt {...}
erstellt wird), wird dessen [[Prototype]]
gemäß der Regel, die wir im vorherigen Kapitel besprochen haben, auf Object.prototype
gesetzt:
Wenn also obj.toString()
aufgerufen wird, wird die Methode von Object.prototype
übernommen.
Wir können es so überprüfen:
sei obj = {}; alarm(obj.__proto__ === Object.prototype); // WAHR alarm(obj.toString === obj.__proto__.toString); //WAHR alarm(obj.toString === Object.prototype.toString); //WAHR
Bitte beachten Sie, dass es in der Kette über Object.prototype
keinen weiteren [[Prototype]]
gibt:
alarm(Object.prototype.__proto__); // null
Andere integrierte Objekte wie Array
, Date
, Function
und andere behalten ebenfalls Methoden in Prototypen.
Wenn wir beispielsweise ein Array [1, 2, 3]
erstellen, wird intern der Standardkonstruktor new Array()
verwendet. Array.prototype
wird also zu seinem Prototyp und stellt Methoden bereit. Das ist sehr speichereffizient.
Laut Spezifikation haben alle integrierten Prototypen oben Object.prototype
. Deshalb sagen manche Leute, dass „alles von Objekten erbt“.
Hier ist das Gesamtbild (für 3 Einbauten):
Lassen Sie uns die Prototypen manuell überprüfen:
sei arr = [1, 2, 3]; // es erbt von Array.prototype? alarm( arr.__proto__ === Array.prototype ); // WAHR // dann von Object.prototype? alarm( arr.__proto__.__proto__ === Object.prototype ); // WAHR // und null oben. alarm( arr.__proto__.__proto__.__proto__ ); // null
Einige Methoden in Prototypen können sich überschneiden. Beispielsweise verfügt Array.prototype
über einen eigenen toString
, der durch Kommas getrennte Elemente auflistet:
sei arr = [1, 2, 3] alarm(arr); // 1,2,3 <-- das Ergebnis von Array.prototype.toString
Wie wir bereits gesehen haben, verfügt Object.prototype
auch über toString
, aber Array.prototype
steht näher in der Kette, sodass die Array-Variante verwendet wird.
Browserinterne Tools wie die Chrome-Entwicklerkonsole zeigen ebenfalls Vererbung an ( console.dir
muss möglicherweise für integrierte Objekte verwendet werden):
Auch andere integrierte Objekte funktionieren auf die gleiche Weise. Sogar Funktionen – sie sind Objekte eines integrierten Function
und ihre Methoden ( call
/ apply
und andere) werden aus Function.prototype
übernommen. Auch Funktionen haben ihren eigenen toString
.
Funktion f() {} alarm(f.__proto__ == Function.prototype); // WAHR Alert(f.__proto__.__proto__ == Object.prototype); // wahr, von Objekten erben
Am kompliziertesten passiert es mit Zeichenfolgen, Zahlen und booleschen Werten.
Wie wir uns erinnern, sind sie keine Objekte. Wenn wir jedoch versuchen, auf ihre Eigenschaften zuzugreifen, werden mithilfe der integrierten Konstruktoren String
, Number
und Boolean
temporäre Wrapper-Objekte erstellt. Sie liefern die Methoden und verschwinden.
Diese Objekte werden für uns unsichtbar erstellt und von den meisten Engines optimiert, aber die Spezifikation beschreibt es genau so. Methoden dieser Objekte befinden sich auch in Prototypen, die als String.prototype
, Number.prototype
und Boolean.prototype
verfügbar sind.
Die Werte null
und undefined
haben keine Objekt-Wrapper
Die Sonderwerte null
und undefined
unterscheiden sich voneinander. Sie haben keine Objekt-Wrapper, daher stehen ihnen keine Methoden und Eigenschaften zur Verfügung. Und es gibt auch keine entsprechenden Prototypen.
Native Prototypen können geändert werden. Wenn wir beispielsweise eine Methode zu String.prototype
hinzufügen, wird sie für alle Strings verfügbar:
String.prototype.show = function() { alarm(this); }; „BOOM!“.show(); // BOOM!
Während des Entwicklungsprozesses haben wir möglicherweise Ideen für neue integrierte Methoden, die wir gerne hätten, und könnten versucht sein, sie zu nativen Prototypen hinzuzufügen. Aber das ist grundsätzlich eine schlechte Idee.
Wichtig:
Prototypen sind global, daher kann es leicht zu Konflikten kommen. Wenn zwei Bibliotheken eine Methode String.prototype.show
hinzufügen, überschreibt eine von ihnen die Methode der anderen.
Daher wird die Änderung eines nativen Prototyps im Allgemeinen als schlechte Idee angesehen.
In der modernen Programmierung gibt es nur einen Fall, in dem die Änderung nativer Prototypen genehmigt wird. Das ist Polyfilling.
Polyfilling ist ein Begriff für die Herstellung eines Ersatzes für eine Methode, die in der JavaScript-Spezifikation vorhanden ist, aber von einer bestimmten JavaScript-Engine noch nicht unterstützt wird.
Wir können es dann manuell implementieren und den integrierten Prototyp damit füllen.
Zum Beispiel:
if (!String.prototype.repeat) { // wenn es keine solche Methode gibt // zum Prototyp hinzufügen String.prototype.repeat = function(n) { // Wiederholen Sie die Zeichenfolge n-mal // Eigentlich sollte der Code etwas komplexer sein // (der vollständige Algorithmus ist in der Spezifikation enthalten) // aber selbst eine unvollständige Polyfüllung wird oft als gut genug angesehen return new Array(n + 1).join(this); }; } alarm( "La".repeat(3) ); // LaLaLa
Im Kapitel Decorators and Forwarding, Call/Apply haben wir über Methoden-Borrowing gesprochen.
Dabei übernehmen wir eine Methode von einem Objekt und kopieren sie in ein anderes.
Einige Methoden nativer Prototypen werden häufig übernommen.
Wenn wir beispielsweise ein Array-ähnliches Objekt erstellen, möchten wir möglicherweise einige Array
-Methoden dorthin kopieren.
Z.B
sei obj = { 0: „Hallo“, 1: „Welt!“, Länge: 2, }; obj.join = Array.prototype.join; alarm( obj.join(',') ); // Hallo, Welt!
Dies funktioniert, weil sich der interne Algorithmus der integrierten join
-Methode nur um die korrekten Indizes und die length
kümmert. Es wird nicht geprüft, ob das Objekt tatsächlich ein Array ist. Viele integrierte Methoden sind so.
Eine andere Möglichkeit besteht darin, zu erben, indem obj.__proto__
auf Array.prototype
gesetzt wird, sodass alle Array
-Methoden automatisch in obj
verfügbar sind.
Aber das ist unmöglich, wenn obj
bereits von einem anderen Objekt erbt. Denken Sie daran, dass wir jeweils nur von einem Objekt erben können.
Die Ausleihmethoden sind flexibel und ermöglichen bei Bedarf die Kombination von Funktionalitäten verschiedener Objekte.
Alle integrierten Objekte folgen demselben Muster:
Die Methoden werden im Prototyp gespeichert ( Array.prototype
, Object.prototype
, Date.prototype
usw.)
Das Objekt selbst speichert nur die Daten (Array-Elemente, Objekteigenschaften, das Datum)
Primitive speichern auch Methoden in Prototypen von Wrapper-Objekten: Number.prototype
, String.prototype
und Boolean.prototype
. Nur undefined
und null
haben keine Wrapper-Objekte
Eingebaute Prototypen können modifiziert oder mit neuen Methoden bestückt werden. Es wird jedoch nicht empfohlen, sie zu ändern. Der einzig zulässige Fall ist wahrscheinlich, wenn wir einen neuen Standard hinzufügen, dieser jedoch noch nicht von der JavaScript-Engine unterstützt wird
Wichtigkeit: 5
Fügen Sie dem Prototyp aller Funktionen die Methode defer(ms)
hinzu, die die Funktion nach ms
Millisekunden ausführt.
Nachdem Sie es getan haben, sollte dieser Code funktionieren:
Funktion f() { alarm("Hallo!"); } f.defer(1000); // zeigt „Hallo!“ nach 1 Sekunde
Function.prototype.defer = function(ms) { setTimeout(this, ms); }; Funktion f() { alarm("Hallo!"); } f.defer(1000); // zeigt „Hallo!“ nach 1 Sek
Wichtigkeit: 4
Fügen Sie dem Prototyp aller Funktionen die Methode defer(ms)
hinzu, die einen Wrapper zurückgibt und den Aufruf um ms
Millisekunden verzögert.
Hier ist ein Beispiel, wie es funktionieren sollte:
Funktion f(a, b) { alarm( a + b ); } f.defer(1000)(1, 2); // zeigt 3 nach 1 Sekunde
Bitte beachten Sie, dass die Argumente an die ursprüngliche Funktion übergeben werden sollten.
Function.prototype.defer = function(ms) { sei f = dies; return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; // überprüfe es Funktion f(a, b) { alarm( a + b ); } f.defer(1000)(1, 2); // zeigt 3 nach 1 Sek
Bitte beachten Sie: Wir verwenden this
in f.apply
, damit unsere Dekoration für Objektmethoden funktioniert.
Wenn also die Wrapper-Funktion als Objektmethode aufgerufen wird, dann wird this
an die ursprüngliche Methode f
übergeben.
Function.prototype.defer = function(ms) { sei f = dies; return function(...args) { setTimeout(() => f.apply(this, args), ms); } }; let user = { Name: „John“, sayHi() { alarm(this.name); } } user.sayHi = user.sayHi.defer(1000); user.sayHi();