JavaScript >> Javascript-Tutorial >  >> Tags >> map

Mitglieder privater Instanzen mit Weakmaps in JavaScript

Letzte Woche stieß ich auf einen Artikel 1 von Nick Fitzgerald, in dem er einen Ansatz zum Erstellen privater Instanzmitglieder für JavaScript-Typen unter Verwendung von ECMAScript 6-Weakmaps beschrieb. Um ganz ehrlich zu sein, war ich nie ein großer Befürworter von Weakmaps – ich dachte, es geht um nichts und es gibt nur einen Anwendungsfall (Tracking von Daten im Zusammenhang mit DOM-Elementen). Ich klammerte mich immer noch fest an diesen Glauben, bis ich Nicks Artikel las, an dem Punkt, an dem mein Weakmap-Glaubenssystem explodierte. Ich sehe jetzt die Möglichkeiten, die Weakmaps für JavaScript bringen, und wie sie unsere Codierungspraktiken auf eine Weise verändern werden, die wir uns wahrscheinlich noch nicht vollständig vorstellen können. Außer dem, den Nick erwähnt hat, das der Schwerpunkt dieses Beitrags ist.

Das Erbe privater Mitglieder

Einer der größten Nachteile von JavaScript ist die Unfähigkeit, wirklich private Instanzmember für benutzerdefinierte Typen zu erstellen. Der einzige gute Weg besteht darin, private Variablen innerhalb eines Konstruktors zu erstellen und privilegierte Methoden zu erstellen, die auf sie zugreifen, wie zum Beispiel:

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

In diesem Beispiel der getName() Methode verwendet den name Argument (effektiv eine lokale Variable), um den Namen der Person zurückzugeben, ohne jemals name verfügbar zu machen als Eigentum. Dieser Ansatz ist in Ordnung, aber höchst ineffizient, wenn Sie eine große Zahl von Person haben Instanzen, weil jede ihre eigene Kopie von getName() tragen muss anstatt eine Methode für den Prototypen zu teilen.

Alternativ können Sie Mitglieder per Konvention privat machen, wie es viele tun, indem Sie dem Mitgliedsnamen einen Unterstrich voranstellen. Der Unterstrich ist keine Magie, er hindert niemanden daran, das Element zu verwenden, sondern dient eher als Erinnerung daran, dass etwas nicht verwendet werden sollte. Zum Beispiel:

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

Person.prototype.getName = function() {
    return this._name;
};

Das Muster hier ist effizienter, da jede Instanz dieselbe Methode für den Prototyp verwendet. Diese Methode greift dann auf this._name zu , die auch außerhalb des Objekts zugänglich ist, aber wir sind uns alle einig, dies nicht zu tun. Dies ist keine ideale Lösung, aber viele Entwickler verlassen sich darauf, um ein gewisses Maß an Schutz zu gewährleisten.

Es gibt auch den Fall gemeinsam genutzter Member über Instanzen hinweg, der einfach mit einem sofort aufgerufenen Funktionsausdruck (IIFE) erstellt werden kann, der einen Konstruktor enthält. Zum Beispiel:

var Person = (function() {

    var sharedName;

    function Person(name) {
        sharedName = name;
    }

    Person.prototype.getName = function() {
        return sharedName;
    };

    return Person;
}());

Hier, sharedName wird von allen Instanzen von Person gemeinsam genutzt , und jede neue Instanz überschreibt den Wert mit dem name das übergeben wird. Dies ist eindeutig ein unsinniges Beispiel, aber es ist ein wichtiger erster Schritt, um zu verstehen, wie man beispielsweise an wirklich private Mitglieder gelangt.

Auf dem Weg zu wirklich privaten Mitgliedern

Das Muster für gemeinsam genutzte private Mitglieder weist auf eine mögliche Lösung hin:Was wäre, wenn die privaten Daten nicht auf der Instanz gespeichert würden, die Instanz aber darauf zugreifen könnte? Was wäre, wenn es ein Objekt gäbe, das mit allen privaten Informationen für eine Instanz versteckt werden könnte? Vor ECMAScript 6 würden Sie so etwas tun:

var Person = (function() {

    var privateData = {},
        privateId = 0;

    function Person(name) {
        Object.defineProperty(this, "_id", { value: privateId++ });

        privateData[this._id] = {
            name: name
        };
    }

    Person.prototype.getName = function() {
        return privateData[this._id].name;
    };

    return Person;
}());

Jetzt kommen wir irgendwo hin. Die privateData Das Objekt ist von außerhalb des IIFE nicht zugänglich, wodurch alle darin enthaltenen Daten vollständig verborgen werden. Die privateId Variable speichert die nächste verfügbare ID, die eine Instanz verwenden kann. Leider muss diese ID auf der Instanz gespeichert werden, daher ist es am besten sicherzustellen, dass sie in keiner Weise geändert werden kann, also verwenden Sie Object.defineProperty() um seinen Anfangswert festzulegen und sicherzustellen, dass die Eigenschaft nicht beschreibbar, konfigurierbar oder aufzählbar ist. Das schützt _id vor Manipulationen. Dann innerhalb von getName() , greift die Methode auf _id zu um die entsprechenden Daten aus dem privaten Datenspeicher zu holen und zurückzugeben.

Dieser Ansatz ist eine ziemlich gute Lösung für das Problem der privaten Daten der Instanz, abgesehen von diesem hässlichen Rest _id die an die Instanz geheftet wird. Dies leidet auch unter dem Problem, dass alle Daten auf Dauer aufbewahrt werden, selbst wenn die Instanz eine Garbage-Collection ist. Dieses Muster ist jedoch das Beste, was wir mit ECMAScript 5 erreichen können.

Weakmap eingeben

Durch das Hinzufügen einer Weakmap in das Bild schmilzt die „fast, aber nicht ganz“ Natur des vorherigen Beispiels dahin. Weakmaps lösen die verbleibenden Probleme privater Datenmitglieder. Erstens besteht keine Notwendigkeit, eine eindeutige ID zu haben, da die Objektinstanz die eindeutige ID ist. Zweitens, wenn eine Objektinstanz speicherbereinigt wird, werden alle Daten, die mit dieser Instanz in der Weakmap verknüpft sind, ebenfalls speicherbereinigt. Das gleiche Grundmuster wie im vorherigen Beispiel kann verwendet werden, aber es ist jetzt viel sauberer:

var Person = (function() {

    var privateData = new WeakMap();

    function Person(name) {
        privateData.set(this, { name: name });
    }

    Person.prototype.getName = function() {
        return privateData.get(this).name;
    };

    return Person;
}());

Die privateData in diesem Beispiel ist es eine Instanz von WeakMap . Wenn ein neuer Person erstellt wird, wird ein Eintrag in der Weakmap für die Instanz vorgenommen, um ein Objekt zu halten, das private Daten enthält. Der Schlüssel in der Weakmap ist this , und obwohl es für einen Entwickler trivial ist, einen Verweis auf einen Person zu erhalten -Objekt gibt es keine Möglichkeit, auf privateData zuzugreifen außerhalb der Instanz, so dass die Daten sicher von Störenfrieden ferngehalten werden. Jede Methode, die die privaten Daten manipulieren möchte, kann dies tun, indem sie die entsprechenden Daten für die gegebene Instanz abruft, indem sie this übergibt und Betrachten des zurückgegebenen Objekts. In diesem Beispiel getName() ruft das Objekt ab und gibt den name zurück Eigentum.

Schlussfolgerung

Ich werde damit abschließen, wie ich angefangen habe:Ich habe mich in Bezug auf Weakmaps geirrt. Ich verstehe jetzt, warum die Leute so begeistert von ihnen waren, und wenn ich sie für nichts anderes verwendet habe, als wirklich private (und nicht hackende) Instanzmitglieder zu erstellen, dann habe ich das Gefühl, dass ich mit ihnen auf meine Kosten gekommen bin. Ich möchte Nick Fitzgerald für seinen Beitrag danken, der mich dazu inspiriert hat, dies zu schreiben, und dafür, dass er mir die Augen für die Möglichkeiten von Weakmaps geöffnet hat. Ich kann mir leicht eine Zukunft vorstellen, in der ich Weakmaps als Teil meines täglichen Toolkits für JavaScript verwende, und ich warte gespannt auf den Tag, an dem wir sie browserübergreifend verwenden können.

Referenzen

  1. Ausblenden von Implementierungsdetails mit ECMAScript 6 WeakMaps von Nick Fitzgerald (fitzgeraldnick.com)