Obwohl JavaScript von Natur aus zwanglos ist, wird die Sprache immer ernster, da Browser immer mehr Dinge tun können. Unter komplexer Logik muss JavaScript modularisiert und Module gekapselt werden, sodass nur Schnittstellen für externe Aufrufe übrig bleiben. Der Abschluss ist der Schlüssel zur Modulkapselung in JavaScript und ein Punkt, der für viele Anfänger schwer zu verstehen ist. Zuerst war ich verwirrt. Jetzt bin ich zuversichtlich, dass ich dieses Konzept besser verstehe. Um das Verständnis zu erleichtern, versucht der Artikel, ein relativ einfaches Objekt zu kapseln.
Wir versuchen, einen Zählerobjekt-Ticker auf der Seite zu pflegen, der einen Wert n beibehält. Während der Benutzer arbeitet, können wir die Anzahl erhöhen (1 zum Wert n addieren), n jedoch nicht dekrementieren oder n direkt ändern. Darüber hinaus müssen wir diesen Wert von Zeit zu Zeit abfragen.
Modularisierung im JSON-Stil mit weit geöffneter Tür
Eine Möglichkeit, die Tür zu öffnen, ist:
var ticker = {
n:0,
tick:function( ){
this.n++;
},
};
Diese Schreibweise ist natürlich und effektiv. Wenn wir die Anzahl erhöhen müssen, rufen wir die Methode ticker.tick() auf, die eine Abfrage erfordert Greifen Sie mal auf die Variable ticker.n zu. Aber auch seine Mängel liegen auf der Hand: Benutzer des Moduls dürfen n frei ändern, indem sie beispielsweise ticker.n-- oder ticker.n=-1 aufrufen. Wir haben ticker nicht gekapselt und scheinen „Mitglieder“ von ticker zu sein, aber ihre Zugänglichkeit ist die gleiche wie ticker, und sie sind global (wenn ticker eine globale Variable ist). In Bezug auf die Kapselung ist dieser modulare Ansatz nur ein kleines bisschen besser als der lächerlichere Ansatz unten (obwohl dieses kleine bisschen für einige einfache Anwendungen ausreicht).
var ticker = {};
var tickerN = 0;
var tickerTick = function(){
tickerN++;
}
tickerTick();
Es ist erwähnenswert, dass ich in tick() auf this.n zugreife – das liegt nicht daran, dass n ein Mitglied von tick() ist, sondern weil es tick() ist, das tick() aufruft. Tatsächlich wäre es besser, ticker.n hier zu schreiben, denn wenn tick() aufgerufen wird, handelt es sich nicht um ticker, sondern um etwas anderes, wie zum Beispiel:
var func = ticker.tick;
func();
Zu diesem Zeitpunkt ist es tatsächlich das Fenster, das tick() aufruft. Wenn die Funktion ausgeführt wird, versucht sie, auf window.n zuzugreifen, und es tritt ein Fehler auf.
Tatsächlich wird dieser modulare Ansatz der „offenen Tür“ häufig zum Organisieren von Daten im JSON-Stil anstelle von Programmen verwendet. Beispielsweise können wir das folgende JSON-Objekt an eine Ticker-Funktion übergeben, um zu bestimmen, dass der Ticker bei 100 beginnt und jedes Mal um 2 vorrückt.
var config = {
nStart:100,
step:2
}
Scope-Kette und Abschluss
Sehen Sie sich den folgenden Code an. Beachten Sie, dass wir Pass bereits implementiert haben in der Konfiguration, um den Ticker anzupassen.
function tick(){
var n = config.nStart;
function tick(){
n += config.step;
}
}
console.log(ticker.n); // ->undefiniert
Sie fragen sich vielleicht, warum sich der Ticker von einem Objekt in eine Funktion geändert hat? Dies liegt daran, dass in JavaScript nur Funktionen einen Gültigkeitsbereich haben und auf Variablen innerhalb der Funktion nicht von außerhalb des Funktionskörpers zugegriffen werden kann. Der Zugriff auf ticker.n außerhalb von tick() führt zu undefined , aber der Zugriff auf n innerhalb von tick() ist kein Problem. Von tick() über ticker() bis hin zum globalen Bereich ist dies die „Bereichskette“ in JavaScript.
Aber es gibt immer noch ein Problem: Wie rufe ich tick() auf? Der Gültigkeitsbereich von tick() maskiert auch tick(). Es gibt zwei Lösungen:
1) Die Methode muss als Rückgabewert aufgerufen werden, genau wie wir die Methode zum Erhöhen von n als Rückgabewert von tick() verwenden.
2) Legen Sie die Variablen fest des äußeren Bereichs, da wir getN in ticker() setzen.
var getN;
function ticker(config){
var n = config.nStart;
getN = function(){
return n;
return function (){
n += config.step;
};
}
tick() ;
console .log(getN()); // ->102
Bitte beachten Sie, dass sich die Variable n derzeit im „Abschluss“ befindet und nicht direkt außerhalb von ticker() aufgerufen werden kann oder manipulieren.
Aber ich habe immer noch das Gefühl, dass etwas nicht stimmt? Was passiert, wenn ich zwei Objekte ticker1 und ticker2 mit derselben Funktionalität verwalten muss? Es gibt nur einen ticker(), also können wir ihn nicht noch einmal schreiben, oder?
neuer Operator und Konstruktor
Wenn Sie eine Funktion über den neuen Operator aufrufen, wird ein neues Objekt erstellt und die Funktion wird mit diesem Objekt aufgerufen. Nach meinem Verständnis ist der Konstruktionsprozess von t1 und t2 im folgenden Code derselbe.
function myClass(){}
var t1 = new myClass();
var t2 = {};
t2.func = myClass;
t2.func();
t2.func = undefiniert;
t1 und t2 sind beide neu konstruierte Objekte und myClass() ist der Konstruktor. Ebenso kann ticker() umgeschrieben werden als
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function(){
n += config.step;
}
}
var ticker1 = new TICKER({nStart:100,step:2});
ticker1 .tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2. tick();
ticker2.tick();
console.log(ticker2.getN()); // ->26
Herkömmlicherweise werden Konstruktoren großgeschrieben. Beachten Sie, dass TICKER() immer noch eine Funktion und kein reines Objekt ist (der Grund, warum wir „rein“ sagen, ist, dass Funktionen tatsächlich Objekte sind und TICKER() ein Funktionsobjekt ist. Der Abschluss ist immer noch gültig und wir können nicht auf ticker1 zugreifen .N .
Prototyp-Prototyp und Vererbung
Der obige TICKER() weist immer noch Mängel auf, das heißt, ticker1.tick() und ticker2.tick() sind unabhängig voneinander! Bitte beachten Sie, dass jedes Mal, wenn Sie den neuen Operator zum Aufrufen von TICKER() verwenden, ein neues Objekt und eine neue Funktion zur Bindung an dieses neue Objekt generiert werden. Jedes Mal, wenn ein neues Objekt erstellt wird, öffnet sich ein Das Speichern von tick() selbst und Variablen innerhalb von tick() ist nicht das, was wir erwarten. Wir erwarten, dass ticker1.tick und ticker2.tick auf dasselbe Funktionsobjekt verweisen.
Dies erfordert die Einführung von Prototypen.
In JavaScript haben außer Object-Objekten auch andere Objekte eine Prototypeigenschaft, die auf ein anderes Objekt verweist. Dieses „andere Objekt“ hat immer noch sein Prototypobjekt und bildet eine Prototypkette, die letztendlich auf das Objektobjekt verweist. Wenn beim Aufrufen einer Methode für ein Objekt festgestellt wird, dass das Objekt keine angegebene Methode hat, suchen Sie in der Prototypenkette bis zum Objektobjekt nach dieser Methode.
Funktionen sind auch Objekte, daher haben Funktionen auch Prototypobjekte. Wenn eine Funktion deklariert wird (dh wenn das Funktionsobjekt definiert wird), wird ein neues Objekt generiert, die Prototypeigenschaft dieses Objekts zeigt auf das Objektobjekt und die Konstruktoreigenschaft dieses Objekts zeigt auf das Funktionsobjekt.
Der Prototyp eines neuen Objekts, das durch einen Konstruktor erstellt wurde, verweist auf das Prototypobjekt des Konstruktors. Wir können also Funktionen zum Prototypobjekt des Konstruktors hinzufügen, und diese Funktionen hängen nicht von ticker1 oder ticker2 ab, sondern von TICKER.
Sie könnten dies tun:
function TICKER(config){
var n = config.nStart;
}
TICKER.prototype.getN = function{
// Achtung: ungültige Implementierung
return n;
};
TICKER.prototype.tick = function{
// Achtung: ungültige Implementierung
n += config.step;
};
Bitte beachten Sie, dass es sich hierbei um eine ungültige Implementierung handelt. Da die Methoden des Prototypobjekts nicht auf den Inhalt des Abschlusses, also auf die Variable n, zugreifen können. Nachdem die TICK()-Methode ausgeführt wurde, kann auf n nicht mehr zugegriffen werden und der Browser zerstört n. Um auf den Inhalt des Abschlusses zuzugreifen, muss das Objekt über einige prägnante instanzabhängige Methoden verfügen, um auf den Inhalt des Abschlusses zuzugreifen, und dann komplexe öffentliche Methoden auf seinem Prototyp definieren, um die Logik zu implementieren. Tatsächlich ist die tick()-Methode im Beispiel prägnant genug. Lassen Sie uns sie wieder in TICKER einfügen. Als nächstes implementieren Sie eine komplexere Methode tickTimes(), die es dem Aufrufer ermöglicht, anzugeben, wie oft tick() aufgerufen wird.
function TICKER(config){
var n = config.nStart;
this.getN = function(){
return n;
};
this.tick = function( ){
n += config.step;
};
}
TICKER.prototype.tickTimes = function(n){
while(n>0){
this. tick();
n--;
}
};
var ticker1 = new TICKER({nStart:100,step:2});
ticker1.tick();
console.log(ticker1.getN()); // ->102
var ticker2 = new TICKER({nStart:20,step:3});
ticker2.tickTimes(2);
console.log(ticker2.getN()); // ->26
Dieser TICKER ist sehr gut. Es kapselt n und kann es nicht direkt von außerhalb des Objekts ändern, und die komplexe Funktion tickTimes() ist auf dem Prototyp definiert. Diese Funktion verarbeitet die Daten im Objekt, indem sie kleine Funktionen der Instanz aufruft.
Um die Kapselung des Objekts aufrechtzuerhalten, schlage ich daher vor, die Datenoperationen in die kleinstmögliche Einheitsfunktion zu entkoppeln und sie im Konstruktor als instanzabhängig (an vielen Stellen auch It genannt) zu definieren ist „privat“) und die komplexe Logik wird auf dem Prototyp implementiert (also „öffentlich“).
Lassen Sie uns abschließend über die Vererbung sprechen. Tatsächlich verwenden wir bereits Vererbung, wenn wir eine Funktion im Prototyp definieren! Die Vererbung in JavaScript ist ... nun ja ... einfacher oder grober als in C++. In C++ können wir eine Tierklasse definieren, um ein Tier darzustellen, und dann eine Vogelklasse definieren, um die Tierklasse zu erben, um einen Vogel darzustellen, aber was ich diskutieren möchte, ist keine solche Vererbung (obwohl eine solche Vererbung auch in JavaScript implementiert werden kann). ); Ich möchte die in C++ besprochene Vererbung darin bestehen, eine Tierklasse zu definieren und dann ein myAnimal-Objekt zu instanziieren. Ja, dies ist eine Instanziierung in C++, wird aber in JavaScript als Vererbung behandelt.
JavaScript unterstützt keine Klassen. Der Browser kümmert sich nur um die aktuell verfügbaren Objekte und kümmert sich nicht darum, welche Klassen diese Objekte sind und welche Struktur sie haben sollten. In unserem Beispiel ist TICKER() ein Funktionsobjekt. Wir können ihm jedoch einen Wert zuweisen (TICKER=1) und löschen (TICKER=undefiniert), da es derzeit zwei Objekte gibt, ticker1 und ticker2 Beim Aufruf fungiert TICKER() als Konstruktor und das TICKER.prototype-Objekt als Klasse.