Geschützte Methoden in JavaScript ES5

In JavaScript wurden viele großartige Artikel über das Objektmodell geschrieben. Und über die verschiedenen Möglichkeiten, private Klassenmitglieder im Internet zu erstellen, gibt es viele wertvolle Beschreibungen. Aber über geschützte Methoden - es gibt sehr wenig Daten. Ich möchte diese Lücke füllen und erläutern, wie Sie geschützte Methoden ohne Bibliotheken in reinem JavaScript ECMAScript 5 erstellen können.

In diesem Artikel:


Link zum Git-Hub-Repository mit Quellcode und Tests.

Warum geschützte Klassenmitglieder benötigt werden


Kurz gesagt

  • Es ist einfacher, die Funktionsweise der Klasse zu verstehen und Fehler darin zu finden. (Sie können sofort sehen, in welchem ​​Fall die Klassenmitglieder verwendet werden. Wenn privat, müssen Sie nur diese Klasse analysieren, und wenn geschützt, dann nur diese und abgeleitete Klassen.)
  • einfacher, Änderungen zu verwalten. (Sie können beispielsweise private Mitglieder entfernen, ohne befürchten zu müssen, dass etwas außerhalb der bearbeitbaren Klasse beschädigt wird.)
  • Die Anzahl der Anwendungen im Bug-Tracker wird reduziert, weil Benutzer der Bibliothek oder des Steuerelements können unsere "privaten" Mitglieder "vernähen", die wir in der neuen Version der Klasse entfernt haben, oder die Logik ihrer Arbeit ändern.
  • Und im Allgemeinen sind geschützte Klassenmitglieder ein Entwurfswerkzeug. Es ist gut, es griffbereit und gut getestet zu haben.

Ich möchte Sie daran erinnern, dass die Hauptidee geschützter Mitglieder darin besteht, Methoden und Eigenschaften vor Benutzern der Klasseninstanz zu verbergen und gleichzeitig abgeleiteten Klassen Zugriff auf sie zu gewähren.

Mit TypeScript können geschützte Methoden nicht aufgerufen werden. Nach der Kompilierung in JavaScript werden jedoch alle privaten und geschützten Mitglieder öffentlich. Beispielsweise entwickeln wir ein Steuerelement oder eine Bibliothek, die Benutzer auf ihren Websites oder Anwendungen installieren. Diese Benutzer können mit geschützten Mitgliedern tun, was sie wollen, was die Integrität der Klasse verletzt. Infolgedessen strotzt unser Bug-Tracker vor Beschwerden, dass unsere Bibliothek oder Steuerung nicht richtig funktioniert. Wir investieren Zeit und Mühe, um das Problem zu lösen. "Befand sich das Objekt auf diese Weise in dem Zustand des Clients, der zu einem Fehler führte ?!" . Um das Leben für alle einfacher zu machen, ist daher ein solcher Schutz erforderlich, der es nicht ermöglicht, die Bedeutung privater und geschützter Klassenmitglieder zu ändern.

Was Sie brauchen, um die betreffende Methode zu verstehen


Um die Methode zum Deklarieren geschützter Klassenmitglieder zu verstehen, benötigen Sie fundierte Kenntnisse:

  • Geräteklassen und Objekte in JavaScript.
  • Möglichkeiten, private Klassenmitglieder zu erstellen (zumindest durch Schließung).
  • Methoden Object.defineProperty und Object.getOwnPropertyDescriptor

Über das Gerätemodell in JavaScript kann ich beispielsweise einen ausgezeichneten Artikel von Andrey Akinshin ( DreamWalker ) Grundlegendes zu OOP in JS [Teil Nr. 1]“ empfehlen .
Über private Immobilien gibt es eine gute und meiner Meinung nach ziemlich vollständige Beschreibung von bis zu 4 verschiedenen Möglichkeiten, private Klassenmitglieder auf der MDN-Website zu erstellen .

Mit der Object.defineProperty-Methode können wir Eigenschaften und Methoden vor For-In-Schleifen und damit vor Serialisierungsalgorithmen verbergen:

function MyClass(){ Object.defineProperty(MyClass.prototype, 'protectedNumber', { value: 12, enumerable: false }); this.publicNumber = 25; }; var obj1 = new MyClass(); for(var prop in obj1){ console.log('property:' prop); //prop     'protectedNumber' } console.log(JSON.stringify(obj1)); //  { 'publicNumber': 25 } 

Eine solche Verschleierung muss durchgeführt werden, aber das reicht natürlich nicht aus, weil Es besteht weiterhin die Möglichkeit, die Methode / Eigenschaft direkt aufzurufen:

  console.log(obj1.protectedNumber); //  12. 

Hilfsklasse ProtectedError


Zunächst benötigen wir die ProtectedError-Klasse, die von Error erbt und ausgelöst wird, wenn kein Zugriff auf die geschützte Methode oder Eigenschaft besteht.

 function ProtectedError(){ this.message = "Encapsulation error, the object member you are trying to address is protected."; } ProtectedError.prototype = new Error(); ProtectedError.prototype.constructor = ProtectedError; 

Implementieren geschützter Klassenmitglieder in ES5


Nachdem wir nun die ProtectedError-Klasse haben und verstehen, was Object.defineProperty mit dem Wert enumerable: false macht, analysieren wir die Erstellung einer Basisklasse, die die protectedMethod-Methode für alle abgeleiteten Klassen freigeben möchte, aber vor allen anderen verbergen:

 function BaseClass(){ if (!(this instanceof BaseClass)) return new BaseClass(); var _self = this; //   ,        /** @summary       */ function checkAccess() { if (!(this instanceof BaseClass)) throw new ProtectedError(); if (this.constructor === BaseClass) throw new ProtectedError() } Object.defineProperty(_self, 'protectedMethod', { enumerable: false, //    for-in  configurable:false, //     value: function(){ //   , ,          Base,     checkAccess.call(this); //  . protectedMethod(); } }); function protectedMethod(){ //       , //       this,   _self return 'example value'; } this.method = function (){ protectedMethod(); //          BaseClass //this.protectedMethod(); //   , ..      ProtectedError } } 

Beschreibung des BaseClass-Klassenkonstruktors


Vielleicht werden Sie durch den Scheck verwirrt:

  if (!(this instanceof BaseClass)) return new BaseClass(); 
Dieser Test ist ein "Amateur". Sie können es entfernen, es hat nichts mit geschützten Methoden zu tun. Ich persönlich lasse es jedoch in meinem Code, weil es wird für jene Fälle benötigt, in denen die Klasseninstanz nicht korrekt erstellt wird, d.h. ohne das Schlüsselwort neu. Zum Beispiel so:

 var obj1 = BaseClass(); //  : var obj2 = BaseClass.call({}); 

Tun Sie in solchen Fällen, was Sie möchten. Sie können beispielsweise einen Fehler generieren:

  if (!(this instanceof BaseClass)) throw new Error('Wrong instance creation. Maybe operator "new" was forgotten'); 

Oder Sie können einfach korrekt instanziieren, wie in BaseClass.

Als nächstes speichern wir die neue Instanz in der Variablen _self (warum ich das später erklären muss).

Beschreibung einer öffentlichen Eigenschaft namens protectedMethod


Wenn wir die Methode eingeben, rufen wir die Kontextprüfung auf, für die wir aufgerufen wurden. Es ist besser, in einer separaten Methode auszuchecken, z. B. checkAccess, weil Die gleiche Prüfung ist für alle geschützten Methoden und Eigenschaften von Klassen erforderlich. Überprüfen Sie zunächst den Kontexttyp des Aufrufs. Wenn dies einen anderen Typ als BaseClass hat, ist der Typ weder BaseClass selbst noch eines seiner Derivate. Wir verbieten solche Anrufe.

 if(!(this instanceof BaseClass)) throw new ProtectedError(); 

Wie kann das passieren? Zum Beispiel so:

 var b = new BaseClass(); var someObject = {}; b.protectedMethod.call(someObject); //   ,  protectedMethod this   someObject    , .. someObject instanceof BaseClass   

Bei abgeleiteten Klassen ist der Ausdruck dieser Instanz von BaseClass wahr. Für BaseClass-Instanzen ist diese Instanz des BaseClass-Ausdrucks jedoch wahr. Um Instanzen der BaseClass-Klasse von Instanzen abgeleiteter Klassen zu unterscheiden, überprüfen wir daher den Konstruktor. Wenn der Konstruktor mit BaseClass übereinstimmt, wird unsere protectedMethod in der BaseClass-Instanz genau wie eine reguläre öffentliche Methode aufgerufen:

 var b = new BaseClass(); b.protectedMethod(); 

Wir verbieten solche Anrufe:

 if(this.constructor === BaseClass) throw new ProtectedError(); 

Als nächstes folgt der Aufruf der geschlossenen Methode protectedMethod, die wir tatsächlich schützen. Wenn Sie innerhalb der Methode auf die Mitglieder der BaseClass-Klasse verweisen müssen, können Sie dies mit der gespeicherten Instanz von _self tun. Dies ist genau das, was _self erstellt wurde, um Zugriff auf Klassenmitglieder aus allen privaten / privaten Methoden zu erhalten. Wenn Sie in Ihrer geschützten Methode oder Eigenschaft nicht auf Klassenmitglieder zugreifen müssen, können Sie daher die Variable _self nicht erstellen.

Aufrufen einer geschützten Methode innerhalb der BaseClass-Klasse


Innerhalb der BaseClass-Klasse darf auf protectedMethod nur über den Namen zugegriffen werden, nicht über diesen. Andernfalls können wir innerhalb von protectedMethod nicht unterscheiden, ob wir als öffentliche Methode oder innerhalb einer Klasse aufgerufen wurden. In diesem Fall spart uns der Abschluss - protectedMethod verhält sich wie eine normale private Methode, die innerhalb der Klasse geschlossen und nur im Rahmen der BaseClass-Funktion sichtbar ist.

DerivedClass Abgeleitete Klassenbeschreibung


Schauen wir uns nun eine abgeleitete Klasse an und wie Sie sie einer geschützten Methode einer Basisklasse zugänglich machen können.

 function DerivedClass(){ var _base = { protectedMethod: this.protectedMethod.bind(this) }; /** @summary       */ function checkAccess() { if (this.constructor === DerivedClass) throw new ProtectedError(); } //     Object.defineProperty(this, 'protectedMethod', { enumerable: false, // ..       this configurable: false,//         for-in  //      value: function(){ checkAccess.call(_self); return _base.protectedMethod(); } }); //        this.someMethod = function(){ console.log(_base.protectedMethod()); } } DerivedClass.prototype = new BaseClass(); Object.defineProperty(DerivedClass.prototype, 'constructor', { value : DerivedClass, configurable: false }); 

Abgeleitete Beschreibung des Klassenkonstruktors


In der abgeleiteten Klasse erstellen wir ein _base-Objekt, in dem wir einen Verweis auf die protectedMethod-Methode der Basisklasse platzieren, die über die Standardbindungsmethode für den Kontext der abgeleiteten Klasse geschlossen ist. Dies bedeutet, dass _base.protectedMethod () aufgerufen wird; In protectedMethod ist dies kein _base-Objekt, sondern eine Instanz der DerivedClass-Klasse.

ProtectedMethod Methodenbeschreibung In DerivedClass


In der DerivedClass-Klasse muss die public-Methode protectedMethod auf dieselbe Weise wie in der Basisklasse über Object.defineProperty deklariert und der Zugriff darauf durch Aufrufen der checkAccess-Methode oder durch direktes Einchecken der Methode überprüft werden:

  Object.defineProperty(DerivedClass.prototype, 'protectedMethod', { enumerable: false, configurable: false, value: function(){ if(this.constructor === DerivedClass) throw new ProtectedError() return _base.protectedMethod(); } }); 

Wir überprüfen - "aber wurden wir als einfache öffentliche Methode aufgerufen?" Für Instanzen der DerivedClass-Klasse entspricht der Konstruktor DerivedClass. Wenn ja, generieren Sie einen Fehler. Andernfalls senden wir es an die Basisklasse und es werden bereits alle anderen Überprüfungen durchgeführt.

In der abgeleiteten Klasse haben wir also zwei Funktionen. Eine wird über Object.defineProperty deklariert und für von DerivedClass abgeleitete Klassen benötigt. Es ist öffentlich und hat daher einen Scheck, der öffentliche Anrufe verbietet. Die zweite Methode befindet sich im _base-Objekt, das innerhalb der DerivedClass-Klasse geschlossen ist und daher von außen für niemanden sichtbar ist. Sie wird verwendet, um von allen DerivedClass-Methoden auf die geschützte Methode zuzugreifen.

Eigentumsschutz


Bei Eigenschaften läuft die Arbeit etwas anders ab. Eigenschaften in BaseClass werden wie gewohnt über Object.defineProperty definiert. Nur in Gettern und Setzern müssen Sie zuerst unsere Prüfung hinzufügen, d. H. Rufen Sie checkAccess auf:

 function BaseClass(){ function checkAccess(){ ... } var _protectedProperty; Object.defineProperty(this, 'protectedProperty', { get: function () { checkAccess.call(this); return _protectedProperty; }, set: function (value) { checkAccess.call(this); _protectedProperty = value; }, enumerable: false, configurable: false }); } 

Innerhalb der BaseClass-Klasse wird nicht über diese, sondern über die geschlossene Variable _protectedProperty auf die protected-Eigenschaft zugegriffen. Wenn es für uns wichtig ist, dass Getter und Setter funktionieren, wenn die Eigenschaft in der BaseClass-Klasse verwendet wird, müssen private Methoden getProtectedPropety und setProtectedProperty erstellt werden, in denen keine Überprüfungen stattfinden und die bereits aufgerufen werden sollten.

 function BaseClass(){ function checkAccess(){ ... } var _protectedProperty; Object.defineProperty(this, 'protectedProperty', { get: function () { checkAccess.call(this); return getProtectedProperty(); }, set: function (value) { checkAccess.call(this); setProtectedProperty(value); }, enumerable: false, configurable: false }); function getProtectedProperty(){ //    return _protectedProperty; } function setProtectedProperty(value){ //    _protectedProperty = value; } } 

In abgeleiteten Klassen ist das Arbeiten mit Eigenschaften etwas komplizierter, weil Eigenschaft kann nicht durch Kontext ersetzt werden. Daher verwenden wir die Standardmethode Object.getOwnPropertyDescriptor, um den Getter und Setter aus der Eigenschaft der Basisklasse als Funktionen abzurufen, die den Aufrufkontext bereits ändern können:

 function DerivedClass(){ function checkAccess(){ ... } var _base = { protectedMethod: _self.protectedMethod.bind(_self), }; var _baseProtectedPropertyDescriptor = Object.getOwnPropertyDescriptor(_self, 'protectedProperty'); //      _base //    DerivedClass     Object.defineProperty(_base, 'protectedProperty', { get: function() { return _baseProtectedPropertyDescriptor.get.call(_self); }, set: function(value){ _baseProtectedPropertyDescriptor.set.call(_self, value); } }) //      ,      DerivedClass      . Object.defineProperty(_self, 'protectedProperty', { get: function () { checkAccess.call(_self); return base.protectedProperty; }, set: function (value) { checkAccess.call(_self); _base.protectedProperty = value; }, enumerable: false, configurable: false }); } 

Vererbungsbeschreibung


Und das Letzte, was ich kommentieren möchte, ist die Vererbung von DerivedClass von BaseClass. Wie Sie vielleicht wissen, ist DerivedClass.prototype = new BaseClass (); erstellt nicht nur einen Prototyp, sondern schreibt auch seine Konstruktoreigenschaft neu. Aus diesem Grund wird die Konstruktoreigenschaft für jede Instanz von DerivedClass gleich BaseClass. Um dies zu beheben, schreiben Sie normalerweise nach dem Erstellen eines Prototyps die Konstruktoreigenschaft neu:

 DerivedClass.prototype = new BaseClass(); DerivedClass.prototype.constructor = DerivedClass; 

Damit jedoch niemand diese Eigenschaft nach uns neu schreibt, verwenden wir dieselbe Object.defineProperty. Die Eigenschaft configable: false verhindert, dass die Eigenschaft erneut überschrieben wird:

 DerivedClass.prototype = new BaseClass(); Object.defineProperty(DerivedClass.prototype, 'constructor', { value : DerivedClass, configurable: false }); 

Source: https://habr.com/ru/post/de425521/


All Articles