Zugriff auf private Mitgliedsvariablen über prototypdefinierte Funktionen

187

Gibt es eine Möglichkeit, "private" Variablen (die im Konstruktor definierten) für prototypdefinierte Methoden verfügbar zu machen?

TestClass = function(){
    var privateField = "hello";
    this.nonProtoHello = function(){alert(privateField)};
};
TestClass.prototype.prototypeHello = function(){alert(privateField)};

Das funktioniert:

t.nonProtoHello()

Das geht aber nicht:

t.prototypeHello()

Ich bin es gewohnt, meine Methoden im Konstruktor zu definieren, aber ich entferne mich aus mehreren Gründen davon.

Morgancodes
quelle
14
@ecampver, außer dieser wurde 2 Jahre zuvor gefragt ....
Pacerier

Antworten:

191

Nein, das gibt es nicht. Das wäre im Wesentlichen umgekehrt.

Im Konstruktor definierte Methoden haben Zugriff auf private Variablen, da alle Funktionen Zugriff auf den Bereich haben, in dem sie definiert wurden.

Auf einem Prototyp definierte Methoden werden nicht im Bereich des Konstruktors definiert und haben keinen Zugriff auf die lokalen Variablen des Konstruktors.

Sie können immer noch private Variablen haben, aber wenn Sie Methoden auf dem Prototyp definiert wollen den Zugang zu ihnen haben, sollten Sie Getter und Setter auf der Definition thisObjekt, das die Prototyp - Methoden (zusammen mit allem anderen) wird Zugang haben. Beispielsweise:

function Person(name, secret) {
    // public
    this.name = name;

    // private
    var secret = secret;

    // public methods have access to private members
    this.setSecret = function(s) {
        secret = s;
    }

    this.getSecret = function() {
        return secret;
    }
}

// Must use getters/setters 
Person.prototype.spillSecret = function() { alert(this.getSecret()); };
Triptychon
quelle
14
"Scoping in Reverse" ist eine C ++ - Funktion mit dem Schlüsselwort "friend". Grundsätzlich sollte jede Funktion ihren Prototyp als Freund definieren. Leider ist dieses Konzept C ++ und nicht JS :(
TWiStErRob
1
Ich möchte diesen Beitrag ganz oben in meine Favoritenliste aufnehmen und dort behalten.
Donato
2
Ich verstehe den Sinn davon nicht - Sie fügen nur eine Abstraktionsebene hinzu, die nichts bewirkt. Sie können auch einfach secreteine Eigenschaft von machen this. JavaScript unterstützt private Variablen mit Prototypen einfach nicht, da Prototypen an den Call-Site-Kontext gebunden sind, nicht an den 'Creation-Site'-Kontext.
nicodemus13
1
Warum nicht einfach person.getSecret()dann?
Fahmi
1
Warum hat das so viele positive Stimmen? Dies macht die Variable nicht privat. Wie oben erwähnt, können Sie mit person.getSecret () von überall auf diese private Variable zugreifen.
Alexr101
63

Update: Mit ES6 gibt es einen besseren Weg:

Kurz gesagt, Sie können das Neue verwenden Symbol, um private Felder zu erstellen.
Hier ist eine großartige Beschreibung: https://curiosity-driven.org/private-properties-in-javascript

Beispiel:

var Person = (function() {
    // Only Person can access nameSymbol
    var nameSymbol = Symbol('name');

    function Person(name) {
        this[nameSymbol] = name;
    }

    Person.prototype.getName = function() {
        return this[nameSymbol];
    };

    return Person;
}());

Für alle modernen Browser mit ES5:

Sie können nur Verschlüsse verwenden

Der einfachste Weg, Objekte zu konstruieren, besteht darin, die prototypische Vererbung insgesamt zu vermeiden. Definieren Sie einfach die privaten Variablen und öffentlichen Funktionen innerhalb des Abschlusses, und alle öffentlichen Methoden haben privaten Zugriff auf die Variablen.

Oder Sie können nur Prototypen verwenden

In JavaScript ist die prototypische Vererbung in erster Linie eine Optimierung . Es ermöglicht mehreren Instanzen, Prototypmethoden gemeinsam zu nutzen, anstatt dass jede Instanz ihre eigenen Methoden hat.
Der Nachteil ist, dass dies thisdas einzige ist, was sich jedes Mal unterscheidet, wenn eine prototypische Funktion aufgerufen wird.
Daher müssen alle privaten Felder über zugänglich sein this, was bedeutet, dass sie öffentlich sind. Wir halten uns also nur an Namenskonventionen für _privateFelder.

Mischen Sie keine Verschlüsse mit Prototypen

Ich denke, Sie sollten Abschlussvariablen nicht mit Prototypmethoden mischen. Sie sollten das eine oder andere verwenden.

Wenn Sie einen Abschluss verwenden, um auf eine private Variable zuzugreifen, können Prototypmethoden nicht auf die Variable zugreifen. Sie müssen also den Verschluss freigebenthis , was bedeutet, dass Sie ihn auf die eine oder andere Weise öffentlich belichten. Mit diesem Ansatz gibt es sehr wenig zu gewinnen.

Welches wähle ich?

Verwenden Sie für wirklich einfache Objekte einfach ein einfaches Objekt mit Verschlüssen.

Wenn Sie eine prototypische Vererbung benötigen - für Vererbung, Leistung usw. -, halten Sie sich an die Namenskonvention "_private" und kümmern Sie sich nicht um Abschlüsse.

Ich verstehe nicht, warum JS-Entwickler sich so sehr bemühen, Felder wirklich privat zu machen.

Scott Rippey
quelle
4
Leider ist die _privateNamenskonvention immer noch die beste Lösung, wenn Sie die prototypische Vererbung nutzen möchten.
Crush
1
ES6 wird ein neues Konzept haben Symbol, das eine hervorragende Möglichkeit ist, private Felder zu erstellen. Hier ist eine gute Erklärung: curiosity-driven.org/private-properties-in-javascript
Scott Rippey
1
Nein, Sie können das Symbolin einem Verschluss aufbewahren, der Ihre gesamte Klasse umfasst. Auf diese Weise können alle Prototypmethoden das Symbol verwenden, es wird jedoch niemals außerhalb der Klasse verfügbar gemacht.
Scott Rippey
2
In dem Artikel, den Sie verlinkt haben, heißt es: " Symbole ähneln privaten Namen, bieten jedoch - anders als private Namen - keine echte Privatsphäre . " Wenn Sie die Instanz haben, können Sie effektiv ihre Symbole mit erhalten Object.getOwnPropertySymbols. Das ist also nur Privatsphäre durch Dunkelheit.
Oriol
2
@Oriol Ja, die Privatsphäre ist durch starke Dunkelheit. Es ist weiterhin möglich, die Symbole zu durchlaufen, und Sie schließen den Zweck des Symbols über ab toString. Dies unterscheidet sich nicht von Java oder C # ... private Mitglieder sind weiterhin über Reflection erreichbar, werden jedoch normalerweise stark verdeckt. Das alles stärkt meinen letzten Punkt: "Ich verstehe nicht, warum JS-Entwickler sich so sehr bemühen, Felder wirklich privat zu machen."
Scott Rippey
31

Als ich das las, klang es wie eine schwierige Herausforderung, also beschloss ich, einen Weg zu finden. Was ich mir ausgedacht habe, war CRAAAAZY , aber es funktioniert total.

Zuerst habe ich versucht, die Klasse in einer Sofortfunktion zu definieren, damit Sie auf einige der privaten Eigenschaften dieser Funktion zugreifen können. Dies funktioniert und ermöglicht es Ihnen, einige private Daten abzurufen. Wenn Sie jedoch versuchen, die privaten Daten festzulegen, werden Sie bald feststellen, dass alle Objekte denselben Wert haben.

var SharedPrivateClass = (function() { // use immediate function
    // our private data
    var private = "Default";

    // create the constructor
    function SharedPrivateClass() {}

    // add to the prototype
    SharedPrivateClass.prototype.getPrivate = function() {
        // It has access to private vars from the immediate function!
        return private;
    };

    SharedPrivateClass.prototype.setPrivate = function(value) {
        private = value;
    };

    return SharedPrivateClass;
})();

var a = new SharedPrivateClass();
console.log("a:", a.getPrivate()); // "a: Default"

var b = new SharedPrivateClass();
console.log("b:", b.getPrivate()); // "b: Default"

a.setPrivate("foo"); // a Sets private to "foo"
console.log("a:", a.getPrivate()); // "a: foo"
console.log("b:", b.getPrivate()); // oh no, b.getPrivate() is "foo"!

console.log(a.hasOwnProperty("getPrivate")); // false. belongs to the prototype
console.log(a.private); // undefined

// getPrivate() is only created once and instanceof still works
console.log(a.getPrivate === b.getPrivate);
console.log(a instanceof SharedPrivateClass);
console.log(b instanceof SharedPrivateClass);

Es gibt viele Fälle, in denen dies angemessen wäre, wenn Sie konstante Werte wie Ereignisnamen haben möchten, die von Instanzen gemeinsam genutzt werden. Im Wesentlichen verhalten sie sich jedoch wie private statische Variablen.

Wenn Sie innerhalb Ihrer im Prototyp definierten Methoden unbedingt Zugriff auf Variablen in einem privaten Namespace benötigen, können Sie dieses Muster ausprobieren.

var PrivateNamespaceClass = (function() { // immediate function
    var instance = 0, // counts the number of instances
        defaultName = "Default Name",  
        p = []; // an array of private objects

    // create the constructor
    function PrivateNamespaceClass() {
        // Increment the instance count and save it to the instance. 
        // This will become your key to your private space.
        this.i = instance++; 
        
        // Create a new object in the private space.
        p[this.i] = {};
        // Define properties or methods in the private space.
        p[this.i].name = defaultName;
        
        console.log("New instance " + this.i);        
    }

    PrivateNamespaceClass.prototype.getPrivateName = function() {
        // It has access to the private space and it's children!
        return p[this.i].name;
    };
    PrivateNamespaceClass.prototype.setPrivateName = function(value) {
        // Because you use the instance number assigned to the object (this.i)
        // as a key, the values set will not change in other instances.
        p[this.i].name = value;
        return "Set " + p[this.i].name;
    };

    return PrivateNamespaceClass;
})();

var a = new PrivateNamespaceClass();
console.log(a.getPrivateName()); // Default Name

var b = new PrivateNamespaceClass();
console.log(b.getPrivateName()); // Default Name

console.log(a.setPrivateName("A"));
console.log(b.setPrivateName("B"));
console.log(a.getPrivateName()); // A
console.log(b.getPrivateName()); // B

// private objects are not accessible outside the PrivateNamespaceClass function
console.log(a.p);

// the prototype functions are not re-created for each instance
// and instanceof still works
console.log(a.getPrivateName === b.getPrivateName);
console.log(a instanceof PrivateNamespaceClass);
console.log(b instanceof PrivateNamespaceClass);

Ich würde mich über Feedback von jedem freuen, der einen Fehler bei dieser Vorgehensweise sieht.

Mims H. Wright
quelle
4
Ich denke, ein mögliches Problem ist, dass jede Instanz unter Verwendung einer anderen Instanz-ID auf private Vars anderer Instanzen zugreifen kann. Nicht unbedingt eine schlechte Sache ...
Mims H. Wright
15
Sie definieren Prototypfunktionen bei jedem Konstruktoraufruf neu
Lu4
10
@ Lu4 Ich bin mir nicht sicher, ob das stimmt. Der Konstruktor wird innerhalb eines Abschlusses zurückgegeben. Das einzige Mal, wenn die Prototypfunktionen definiert werden, ist das erste Mal in diesem sofort aufgerufenen Funktionsausdruck. Abgesehen von den oben genannten Datenschutzproblemen sieht das für mich gut aus (auf den ersten Blick).
Guypursey
1
@ MimsH.Wright andere Sprachen ermöglichen den Zugriff auf andere Objekte derselben Klasse , jedoch nur, wenn Sie auf sie verweisen. Um dies zu ermöglichen, können Sie die Privaten hinter einer Funktion verbergen, die den Objektzeiger als Schlüssel verwendet (wie an eine ID angehängt). Auf diese Weise haben Sie nur Zugriff auf private Daten von Objekten, die Sie kennen. Dies entspricht eher dem Umfang in anderen Sprachen. Diese Implementierung wirft jedoch ein Licht auf ein tieferes Problem. Die privaten Objekte werden erst dann mit Garbage Collected erfasst, wenn die Konstruktorfunktion aktiviert ist.
Thomas Nadin
3
Ich möchte erwähnen, dass idies allen Instanzen hinzugefügt wurde. Es ist also nicht vollständig "transparent" und ikönnte dennoch manipuliert werden.
Scott Rippey
18

siehe Doug Crockfords Seite dazu . Sie müssen dies indirekt mit etwas tun, das auf den Bereich der privaten Variablen zugreifen kann.

ein anderes Beispiel:

Incrementer = function(init) {
  var counter = init || 0;  // "counter" is a private variable
  this._increment = function() { return counter++; }
  this._set = function(x) { counter = x; }
}
Incrementer.prototype.increment = function() { return this._increment(); }
Incrementer.prototype.set = function(x) { return this._set(x); }

Anwendungsfall:

js>i = new Incrementer(100);
[object Object]
js>i.increment()
100
js>i.increment()
101
js>i.increment()
102
js>i.increment()
103
js>i.set(-44)
js>i.increment()
-44
js>i.increment()
-43
js>i.increment()
-42
Jason S.
quelle
47
Dieses Beispiel scheint eine schreckliche Praxis zu sein. Bei der Verwendung von Prototypmethoden müssen Sie nicht für jede Instanz eine neue erstellen. Das machst du sowieso. Für jede Methode erstellen Sie eine andere.
Kir
2
@ArmedMonkey Das Konzept sieht gut aus, ist sich aber einig, dass dies ein schlechtes Beispiel ist, da die gezeigten Prototypfunktionen trivial sind. Wenn die Prototypfunktionen viel längere Funktionen wären, die einen einfachen Zugriff auf die "privaten" Variablen erfordern, wäre dies sinnvoll.
Pfannkuchen
9
Warum sich überhaupt die Mühe machen, _setüber zu belichten set? Warum setnennst du es nicht gleich ?
Scott Rippey
15

Ich schlage vor, es wäre wahrscheinlich eine gute Idee, "eine Prototypzuweisung in einem Konstruktor zu haben" als ein Javascript-Anti-Pattern zu beschreiben. Denk darüber nach. Es ist viel zu riskant.

Was Sie dort beim Erstellen des zweiten Objekts (dh b) tatsächlich tun, ist die Neudefinition dieser Prototypfunktion für alle Objekte, die diesen Prototyp verwenden. Dadurch wird der Wert für Objekt a in Ihrem Beispiel effektiv zurückgesetzt. Es funktioniert, wenn Sie eine gemeinsam genutzte Variable möchten und zufällig alle Objektinstanzen im Voraus erstellen, aber es fühlt sich viel zu riskant an.

Ich habe in einem Javascript, an dem ich kürzlich gearbeitet habe, einen Fehler gefunden, der genau auf dieses Anti-Muster zurückzuführen ist. Es wurde versucht, einen Drag & Drop-Handler für das zu erstellende Objekt festzulegen, dies wurde jedoch für alle Instanzen durchgeführt. Nicht gut.

Doug Crockfords Lösung ist die beste.

Lance Ewing
quelle
10

@ Kai

Das wird nicht funktionieren. Wenn Sie tun

var t2 = new TestClass();

Dann t2.prototypeHellowird auf den privaten Bereich von t zugegriffen.

@AnglesCrimes

Der Beispielcode funktioniert einwandfrei, erstellt jedoch tatsächlich ein "statisches" privates Mitglied, das von allen Instanzen gemeinsam genutzt wird. Es ist möglicherweise nicht die Lösung, nach der Morgancodes gesucht haben.

Bisher habe ich keinen einfachen und sauberen Weg gefunden, dies zu tun, ohne einen privaten Hash und zusätzliche Bereinigungsfunktionen einzuführen. Eine private Mitgliedsfunktion kann bis zu einem gewissen Grad simuliert werden:

(function() {
    function Foo() { ... }
    Foo.prototype.bar = function() {
       privateFoo.call(this, blah);
    };
    function privateFoo(blah) { 
        // scoped to the instance by passing this to call 
    }

    window.Foo = Foo;
}());
Tim
quelle
Haben Sie Ihre Punkte klar verstanden, aber können Sie bitte erklären, was Ihr Code-Snippet versucht zu tun?
Vishwanath
privateFooist völlig privat und somit unsichtbar, wenn man eine bekommt new Foo(). Nur hier bar()gibt es eine öffentliche Methode, auf die zugegriffen werden kann privateFoo. Sie können denselben Mechanismus für einfache Variablen und Objekte verwenden. Beachten Sie jedoch immer, dass diese privatestatsächlich statisch sind und von allen von Ihnen erstellten Objekten gemeinsam genutzt werden.
Philzen
6

Ja es ist möglich. Das PPF-Designmuster löst dieses Problem.

PPF steht für Private Prototype Functions. Basic PPF löst diese Probleme:

  1. Prototypfunktionen erhalten Zugriff auf private Instanzdaten.
  2. Prototypfunktionen können privatisiert werden.

Zum ersten nur:

  1. Fügen Sie alle privaten Instanzvariablen, auf die Sie über Prototypfunktionen zugreifen möchten, in einen separaten Datencontainer ein
  2. Übergeben Sie als Parameter einen Verweis auf den Datencontainer an alle Prototypfunktionen.

So einfach ist das. Beispielsweise:

// Helper class to store private data.
function Data() {};

// Object constructor
function Point(x, y)
{
  // container for private vars: all private vars go here
  // we want x, y be changeable via methods only
  var data = new Data;
  data.x = x;
  data.y = y;

  ...
}

// Prototype functions now have access to private instance data
Point.prototype.getX = function(data)
{
  return data.x;
}

Point.prototype.getY = function(data)
{
  return data.y;
}

...

Lesen Sie die ganze Geschichte hier:

PPF-Entwurfsmuster

Edward
quelle
4
Nur-Link-Antworten sind bei SO im Allgemeinen verpönt. Bitte zeigen Sie ein Beispiel.
Corey Adler
Der Artikel enthält Beispiele, also siehe dort
Edward
5
Was passiert jedoch, wenn diese Site irgendwann später ausfällt? Wie soll dann jemand ein Beispiel sehen? Die Richtlinie ist vorhanden, damit hier alles Wertvolle in einem Link aufbewahrt werden kann und Sie sich nicht auf eine Website verlassen müssen, auf die wir keinen Einfluss haben.
Corey Adler
3
@ Edward, dein Link ist eine interessante Lektüre! Es scheint mir jedoch, dass der Hauptgrund für den Zugriff auf private Daten mithilfe prototypischer Funktionen darin besteht, zu verhindern, dass jedes Objekt Speicher mit identischen öffentlichen Funktionen verschwendet. Die von Ihnen beschriebene Methode löst dieses Problem nicht, da für die öffentliche Verwendung eine prototypische Funktion in eine reguläre öffentliche Funktion eingeschlossen werden muss. Ich denke, das Muster könnte nützlich sein, um Speicherplatz zu sparen, wenn Sie viele ppfs haben, die in einer einzigen öffentlichen Funktion kombiniert sind. Verwenden Sie sie für etwas anderes?
Dining Philosopher
@DiningPhilosofer, danke, dass du meinen Artikel geschätzt hast. Ja, Sie haben Recht, wir verwenden weiterhin Instanzfunktionen. Die Idee ist jedoch, sie so leicht wie möglich zu machen, indem Sie einfach ihre PPF-Kollegen erneut anrufen, die die ganze schwere Arbeit erledigen. Schließlich rufen alle Instanzen dieselben PPFs auf (natürlich über Wrapper), sodass eine gewisse Speicherersparnis zu erwarten ist. Die Frage ist, wie viel. Ich erwarte erhebliche Einsparungen.
Edward
5

Sie können dies tatsächlich mithilfe der Accessor-Überprüfung erreichen :

(function(key, global) {
  // Creates a private data accessor function.
  function _(pData) {
    return function(aKey) {
      return aKey === key && pData;
    };
  }

  // Private data accessor verifier.  Verifies by making sure that the string
  // version of the function looks normal and that the toString function hasn't
  // been modified.  NOTE:  Verification can be duped if the rogue code replaces
  // Function.prototype.toString before this closure executes.
  function $(me) {
    if(me._ + '' == _asString && me._.toString === _toString) {
      return me._(key);
    }
  }
  var _asString = _({}) + '', _toString = _.toString;

  // Creates a Person class.
  var PersonPrototype = (global.Person = function(firstName, lastName) {
    this._ = _({
      firstName : firstName,
      lastName : lastName
    });
  }).prototype;
  PersonPrototype.getName = function() {
    var pData = $(this);
    return pData.firstName + ' ' + pData.lastName;
  };
  PersonPrototype.setFirstName = function(firstName) {
    var pData = $(this);
    pData.firstName = firstName;
    return this;
  };
  PersonPrototype.setLastName = function(lastName) {
    var pData = $(this);
    pData.lastName = lastName;
    return this;
  };
})({}, this);

var chris = new Person('Chris', 'West');
alert(chris.setFirstName('Christopher').setLastName('Webber').getName());

Dieses Beispiel stammt aus meinem Beitrag über prototypische Funktionen und private Daten und wird dort ausführlicher erläutert.

Chris West
quelle
1
Diese Antwort ist zu "klug", um nützlich zu sein, aber ich mag die Antwort, eine IFFE-gebundene Variable als geheimen Handschlag zu verwenden. Diese Implementierung verwendet zu viele Abschlüsse, um nützlich zu sein. Der Zweck von prototypdefinierten Methoden besteht darin, die Erstellung neuer Funktionsobjekte für jede Methode für jedes Objekt zu verhindern.
Greg.kindel
Dieser Ansatz verwendet einen geheimen Schlüssel, um zu identifizieren, welche Prototypmethoden vertrauenswürdig sind und welche nicht. Es ist jedoch die Instanz, die den Schlüssel validiert. Daher muss der Schlüssel an die Instanz gesendet werden. Aber dann könnte nicht vertrauenswürdiger Code eine vertrauenswürdige Methode für eine gefälschte Instanz aufrufen, die den Schlüssel stehlen würde. Erstellen Sie mit diesem Schlüssel neue Methoden, die von realen Instanzen als vertrauenswürdig angesehen werden. Das ist also nur Privatsphäre durch Dunkelheit.
Oriol
4

In aktuellen JavaScript, ich bin ziemlich sicher , dass es ein und nur ein Weg , um privat Zustand , zugänglich von Prototyp - Funktionen, ohne etwas hinzuzufügen Öffentlichkeit zu this. Die Antwort ist, das Muster "schwache Karte" zu verwenden.

Um es zusammenzufassen: Die PersonKlasse hat eine einzige schwache Zuordnung, wobei die Schlüssel die Instanzen von Person sind und die Werte einfache Objekte sind, die für die private Speicherung verwendet werden.

Hier ist ein voll funktionsfähiges Beispiel: (Spielen Sie unter http://jsfiddle.net/ScottRippey/BLNVr/ )

var Person = (function() {
    var _ = weakMap();
    // Now, _(this) returns an object, used for private storage.
    var Person = function(first, last) {
        // Assign private storage:
        _(this).firstName = first;
        _(this).lastName = last;
    }
    Person.prototype = {
        fullName: function() {
            // Retrieve private storage:
            return _(this).firstName + _(this).lastName;
        },
        firstName: function() {
            return _(this).firstName;
        },
        destroy: function() {
            // Free up the private storage:
            _(this, true);
        }
    };
    return Person;
})();

function weakMap() {
    var instances=[], values=[];
    return function(instance, destroy) {
        var index = instances.indexOf(instance);
        if (destroy) {
            // Delete the private state:
            instances.splice(index, 1);
            return values.splice(index, 1)[0];
        } else if (index === -1) {
            // Create the private state:
            instances.push(instance);
            values.push({});
            return values[values.length - 1];
        } else {
            // Return the private state:
            return values[index];
        }
    };
}

Wie gesagt, dies ist wirklich der einzige Weg, um alle 3 Teile zu erreichen.

Es gibt jedoch zwei Einschränkungen. Erstens kostet dies die Leistung - jedes Mal, wenn Sie auf die privaten Daten zugreifen, handelt es sich um einen O(n)Vorgang, bei dem ndie Anzahl der Instanzen angegeben wird. Sie möchten dies also nicht tun, wenn Sie eine große Anzahl von Instanzen haben. Zweitens müssen Sie aufrufen, wenn Sie mit einer Instanz fertig sinddestroy . Andernfalls werden die Instanz und die Daten nicht durch Müll gesammelt, und es kommt zu einem Speicherverlust.

Und deshalb möchte ich mich an meine ursprüngliche Antwort "Du solltest nicht" halten.

Scott Rippey
quelle
Wenn Sie eine Instanz von Person nicht explizit zerstören, bevor sie außerhalb des Gültigkeitsbereichs liegt, enthält die Schwachkarte dann keinen Verweis darauf, sodass ein Speicherverlust auftritt. Ich habe ein Muster für protected entwickelt, da andere Instanzen von Person auf die Variable zugreifen können und diejenigen, die von Person erben, dies können. Ich habe es nur durchgespielt, bin mir also nicht sicher, ob es andere Nachteile als zusätzliche Verarbeitung gibt (sieht nicht so aus wie der Zugriff auf die Privaten). Stackoverflow.com/a/21800194/1641941 Das Zurückgeben eines privaten / geschützten Objekts ist ein Problem, da Code aufgerufen wird kann dann Ihre privaten / geschützten mutieren.
HMR
2
@HMR Ja, Sie müssen die privaten Daten explizit zerstören. Ich werde diese Einschränkung zu meiner Antwort hinzufügen.
Scott Rippey
3

Es gibt einen einfacheren Weg, indem Sie die Verwendung von bindund callMethoden nutzen.

Durch Festlegen privater Variablen für ein Objekt können Sie den Bereich dieses Objekts nutzen.

Beispiel

function TestClass (value) {
    // The private value(s)
    var _private = {
        value: value
    };

    // `bind` creates a copy of `getValue` when the object is instantiated
    this.getValue = TestClass.prototype.getValue.bind(_private);

    // Use `call` in another function if the prototype method will possibly change
    this.getValueDynamic = function() {
        return TestClass.prototype.getValue.call(_private);
    };
};

TestClass.prototype.getValue = function() {
    return this.value;
};

Diese Methode ist nicht ohne Nachteile. Da der Bereichskontext effektiv überschrieben wird, haben Sie keinen Zugriff außerhalb des _privateObjekts. Es ist jedoch nicht unmöglich, weiterhin Zugriff auf den Bereich des Instanzobjekts zu gewähren. Sie können den Kontext ( this) des Objekts als zweites Argument an die öffentlichen Werte in der Prototypfunktion übergeben bindoder callweiterhin darauf zugreifen.

Zugang zu öffentlichen Werten

function TestClass (value) {
    var _private = {
        value: value
    };

    this.message = "Hello, ";

    this.getMessage = TestClass.prototype.getMessage.bind(_private, this);

}

TestClass.prototype.getMessage = function(_public) {

    // Can still access passed in arguments
    // e.g. – test.getValues('foo'), 'foo' is the 2nd argument to the method
    console.log([].slice.call(arguments, 1));
    return _public.message + this.value;
};

var test = new TestClass("World");
test.getMessage(1, 2, 3); // [1, 2, 3]         (console.log)
                          // => "Hello, World" (return value)

test.message = "Greetings, ";
test.getMessage(); // []                    (console.log)
                   // => "Greetings, World" (return value)
thgaskell
quelle
2
Warum sollte jemand eine Kopie der Prototypmethode erstellen, anstatt zunächst nur eine instanziierte Methode zu erstellen?
Crush
3

Versuch es!

    function Potatoe(size) {
    var _image = new Image();
    _image.src = 'potatoe_'+size+'.png';
    function getImage() {
        if (getImage.caller == null || getImage.caller.owner != Potatoe.prototype)
            throw new Error('This is a private property.');
        return _image;
    }
    Object.defineProperty(this,'image',{
        configurable: false,
        enumerable: false,
        get : getImage          
    });
    Object.defineProperty(this,'size',{
        writable: false,
        configurable: false,
        enumerable: true,
        value : size            
    });
}
Potatoe.prototype.draw = function(ctx,x,y) {
    //ctx.drawImage(this.image,x,y);
    console.log(this.image);
}
Potatoe.prototype.draw.owner = Potatoe.prototype;

var pot = new Potatoe(32);
console.log('Potatoe size: '+pot.size);
try {
    console.log('Potatoe image: '+pot.image);
} catch(e) {
    console.log('Oops: '+e);
}
pot.draw();
AlanNLohse
quelle
1
Dies callerhängt von einer implementierungsabhängigen Erweiterung ab, die im strengen Modus nicht zulässig ist.
Oriol
1

Folgendes habe ich mir ausgedacht.

(function () {
    var staticVar = 0;
    var yrObj = function () {
        var private = {"a":1,"b":2};
        var MyObj = function () {
            private.a += staticVar;
            staticVar++;
        };
        MyObj.prototype = {
            "test" : function () {
                console.log(private.a);
            }
        };

        return new MyObj;
    };
    window.YrObj = yrObj;
}());

var obj1 = new YrObj;
var obj2 = new YrObj;
obj1.test(); // 1
obj2.test(); // 2

Das Hauptproblem bei dieser Implementierung besteht darin, dass die Prototypen bei jeder Instanziierung neu definiert werden.

Xeltor
quelle
Interessant, ich mag den Versuch wirklich und habe an dasselbe gedacht, aber Sie haben Recht, dass die Neudefinition der Prototypfunktion bei jeder Instanziierung eine ziemlich große Einschränkung darstellt. Dies liegt nicht nur daran, dass CPU-Zyklen verschwendet werden, sondern auch daran, dass der Prototoype, wenn Sie ihn später ändern, bei der nächsten Instanziierung auf den im Konstruktor definierten ursprünglichen Zustand zurückgesetzt wird: /
Niko Bellic
1
Dies definiert nicht nur Prototypen neu, sondern definiert für jede Instanz einen neuen Konstruktor. Die "Instanzen" sind also keine Instanzen derselben Klasse mehr.
Oriol
1

Es gibt einen sehr einfachen Weg, dies zu tun

function SharedPrivate(){
  var private = "secret";
  this.constructor.prototype.getP = function(){return private}
  this.constructor.prototype.setP = function(v){ private = v;}
}

var o1 = new SharedPrivate();
var o2 = new SharedPrivate();

console.log(o1.getP()); // secret
console.log(o2.getP()); // secret
o1.setP("Pentax Full Frame K1 is on sale..!");
console.log(o1.getP()); // Pentax Full Frame K1 is on sale..!
console.log(o2.getP()); // Pentax Full Frame K1 is on sale..!
o2.setP("And it's only for $1,795._");
console.log(o1.getP()); // And it's only for $1,795._

JavaScript-Prototypen sind golden.

Reduzieren
quelle
2
Ich glaube, es ist besser, keinen Prototyp in der Konstruktorfunktion zu verwenden, da jedes Mal, wenn eine neue Instanz erstellt wird, eine neue Funktion erstellt wird.
Whamsicore
@whamsicore Ja, aber in diesem Fall ist es wichtig, dass wir für jedes einzelne instanziierte Objekt einen gemeinsamen Abschluss arrangieren müssen. Das ist der Grund, warum sich die Funktionsdefinitionen im Konstruktor befinden und wir uns auf das beziehen müssen, SharedPrivate.prototypeda this.constructor.prototypees keine große Sache ist, getP und setP mehrmals neu zu definieren ...
Redu
1

Ich bin zu spät zur Party, aber ich denke, ich kann dazu beitragen. Hier, sehen Sie sich das an:

// 1. Create closure
var SomeClass = function() {
  // 2. Create `key` inside a closure
  var key = {};
  // Function to create private storage
  var private = function() {
    var obj = {};
    // return Function to access private storage using `key`
    return function(testkey) {
      if(key === testkey) return obj;
      // If `key` is wrong, then storage cannot be accessed
      console.error('Cannot access private properties');
      return undefined;
    };
  };
  var SomeClass = function() {
    // 3. Create private storage
    this._ = private();
    // 4. Access private storage using the `key`
    this._(key).priv_prop = 200;
  };
  SomeClass.prototype.test = function() {
    console.log(this._(key).priv_prop); // Using property from prototype
  };
  return SomeClass;
}();

// Can access private property from within prototype
var instance = new SomeClass();
instance.test(); // `200` logged

// Cannot access private property from outside of the closure
var wrong_key = {};
instance._(wrong_key); // undefined; error logged

Ich nenne diese Methode Accessor-Muster . Die wesentliche Idee ist, dass wir einen Abschluss haben , einen Schlüssel innerhalb des Abschlusses, und wir erstellen ein privates Objekt (im Konstruktor), auf das nur zugegriffen werden kann, wenn Sie den Schlüssel haben .

Wenn Sie interessiert sind, können Sie mehr darüber in meinem Artikel lesen . Mit dieser Methode können Sie Objekteigenschaften erstellen, auf die außerhalb des Abschlusses nicht zugegriffen werden kann. Daher können Sie sie im Konstruktor oder Prototyp verwenden, aber nirgendwo anders. Ich habe diese Methode nirgendwo gesehen, aber ich denke, sie ist wirklich mächtig.

Guitarino
quelle
0

Können Sie die Variablen nicht in einen höheren Bereich stellen?

(function () {
    var privateVariable = true;

    var MyClass = function () {
        if (privateVariable) console.log('readable from private scope!');
    };

    MyClass.prototype.publicMethod = function () {
        if (privateVariable) console.log('readable from public scope!');
    };
}))();
Ev Haus
quelle
4
Anschließend werden die Variablen von allen Instanzen von MyClass gemeinsam genutzt.
Crush
0

Sie können auch versuchen, eine Methode nicht direkt für den Prototyp, sondern für die Konstruktorfunktion wie folgt hinzuzufügen:

var MyArray = function() {
    var array = [];

    this.add = MyArray.add.bind(null, array);
    this.getAll = MyArray.getAll.bind(null, array);
}

MyArray.add = function(array, item) {
    array.push(item);
}
MyArray.getAll = function(array) {
    return array;
}

var myArray1 = new MyArray();
myArray1.add("some item 1");
console.log(myArray1.getAll()); // ['some item 1']
var myArray2 = new MyArray();
myArray2.add("some item 2");
console.log(myArray2.getAll()); // ['some item 2']
console.log(myArray1.getAll()); // ['some item 2'] - FINE!
Maciej Dzikowicki
quelle
0

Hier ist etwas, das ich mir ausgedacht habe, als ich versucht habe, die einfachste Lösung für dieses Problem zu finden. Vielleicht könnte es für jemanden nützlich sein. Ich bin neu in Javascript, daher kann es durchaus zu Problemen mit dem Code kommen.

// pseudo-class definition scope
(function () {

    // this is used to identify 'friend' functions defined within this scope,
    // while not being able to forge valid parameter for GetContext() 
    // to gain 'private' access from outside
    var _scope = new (function () { })();
    // -----------------------------------------------------------------

    // pseudo-class definition
    this.Something = function (x) {

        // 'private' members are wrapped into context object,
        // it can be also created with a function
        var _ctx = Object.seal({

            // actual private members
            Name: null,
            Number: null,

            Somefunc: function () {
                console.log('Something(' + this.Name + ').Somefunc(): number = ' + this.Number);
            }
        });
        // -----------------------------------------------------------------

        // function below needs to be defined in every class
        // to allow limited access from prototype
        this.GetContext = function (scope) {

            if (scope !== _scope) throw 'access';
            return _ctx;
        }
        // -----------------------------------------------------------------

        {
            // initialization code, if any
            _ctx.Name = (x !== 'undefined') ? x : 'default';
            _ctx.Number = 0;

            Object.freeze(this);
        }
    }
    // -----------------------------------------------------------------

    // prototype is defined only once
    this.Something.prototype = Object.freeze({

        // public accessors for 'private' field
        get Number() { return this.GetContext(_scope).Number; },
        set Number(v) { this.GetContext(_scope).Number = v; },

        // public function making use of some private fields
        Test: function () {

            var _ctx = this.GetContext(_scope);
            // access 'private' field
            console.log('Something(' + _ctx.Name + ').Test(): ' + _ctx.Number);
            // call 'private' func
            _ctx.Somefunc();
        }
    });
    // -----------------------------------------------------------------

    // wrap is used to hide _scope value and group definitions
}).call(this);

function _A(cond) { if (cond !== true) throw new Error('assert failed'); }
// -----------------------------------------------------------------

function test_smth() {

    console.clear();

    var smth1 = new Something('first'),
      smth2 = new Something('second');

    //_A(false);
    _A(smth1.Test === smth2.Test);

    smth1.Number = 3;
    smth2.Number = 5;
    console.log('smth1.Number: ' + smth1.Number + ', smth2.Number: ' + smth2.Number);

    smth1.Number = 2;
    smth2.Number = 6;

    smth1.Test();
    smth2.Test();

    try {
        var ctx = smth1.GetContext();
    } catch (err) {
        console.log('error: ' + err);
    }
}

test_smth();
V.Mihaly4
quelle
0

Ich hatte heute genau die gleiche Frage und nachdem ich die erstklassige Antwort von Scott Rippey ausgearbeitet hatte, kam ich auf eine sehr einfache Lösung (IMHO), die sowohl mit ES5 kompatibel als auch effizient ist. Sie ist auch sicher gegen Namenskonflikte (die Verwendung von _private scheint unsicher). .

/*jslint white: true, plusplus: true */

 /*global console */

var a, TestClass = (function(){
    "use strict";
    function PrefixedCounter (prefix) {
        var counter = 0;
        this.count = function () {
            return prefix + (++counter);
        };
    }
    var TestClass = (function(){
        var cls, pc = new PrefixedCounter("_TestClass_priv_")
        , privateField = pc.count()
        ;
        cls = function(){
            this[privateField] = "hello";
            this.nonProtoHello = function(){
                console.log(this[privateField]);
            };
        };
        cls.prototype.prototypeHello = function(){
            console.log(this[privateField]);
        };
        return cls;
    }());
    return TestClass;
}());

a = new TestClass();
a.nonProtoHello();
a.prototypeHello();

Getestet mit Ringojs und Nodejs. Ich bin gespannt auf Ihre Meinung.

alexgirao
quelle
Hier ist eine Referenz: Überprüfen Sie den Abschnitt "Ein Schritt näher". philipwalton.com/articles/…
jimasun
0
var getParams = function(_func) {
  res = _func.toString().split('function (')[1].split(')')[0].split(',')
  return res
}

function TestClass(){

  var private = {hidden: 'secret'}
  //clever magic accessor thing goes here
  if ( !(this instanceof arguments.callee) ) {
    for (var key in arguments) {
      if (typeof arguments[key] == 'function') {
        var keys = getParams(arguments[key])
        var params = []
        for (var i = 0; i <= keys.length; i++) {
          if (private[keys[i]] != undefined) {
            params.push(private[keys[i]])
          }
        }
        arguments[key].apply(null,params)
      }
    }
  }
}


TestClass.prototype.test = function(){
  var _hidden; //variable I want to get
  TestClass(function(hidden) {_hidden = hidden}) //invoke magic to get
};

new TestClass().test()

Wie ist das? Verwenden eines privaten Accessors. Je nach Anwendungsfall können Sie die Variablen nur abrufen, aber nicht festlegen.

dylan0150
quelle
Dies gilt nicht die Frage beantworten , in einer sinnvollen Art und Weise. Warum glaubst du, ist das die Antwort? wie funktioniert es Wenn Sie einfach jemandem sagen, er solle seinen Code ohne Kontext oder Bedeutung ändern , lernen Sie nicht, was er falsch gemacht hat.
GrumpyCrouton
Er wollte eine Möglichkeit, über Prototypen auf versteckte private Variablen einer Klasse zuzugreifen, ohne diese versteckte Variable auf jeder einzelnen Instanz der Klasse erstellen zu müssen. Der obige Code ist eine beispielhafte Methode, um dies zu tun. Wie ist das keine Antwort auf die Frage?
Dylan0150
Ich habe nicht gesagt, dass es keine Antwort auf die Frage ist. Ich sagte, es sei kein nützliche Antwort, weil es niemandem beim Lernen hilft. Sie sollten Ihren Code erklären, warum er funktioniert und warum dies der richtige Weg ist. Wenn ich der Autor der Frage wäre, würde ich Ihre Antwort nicht akzeptieren, da sie nicht zum Lernen anregt, mir nicht beibringt, was ich falsch mache oder was der angegebene Code tut oder wie er funktioniert.
GrumpyCrouton
0

Ich habe eine Lösung, bin mir aber nicht sicher, ob sie fehlerfrei ist.

Damit es funktioniert, müssen Sie die folgende Struktur verwenden:

  1. Verwenden Sie 1 privates Objekt, das alle privaten Variablen enthält.
  2. Verwenden Sie eine Instanzfunktion.
  3. Wenden Sie einen Verschluss auf den Konstruktor und alle Prototypfunktionen an.
  4. Jede erstellte Instanz erfolgt außerhalb des definierten Abschlusses.

Hier ist der Code:

var TestClass = 
(function () {
    // difficult to be guessed.
    var hash = Math.round(Math.random() * Math.pow(10, 13) + + new Date());
    var TestClass = function () {
        var privateFields = {
            field1: 1,
            field2: 2
        };
        this.getPrivateFields = function (hashed) {
            if(hashed !== hash) {
                throw "Cannot access private fields outside of object.";
                // or return null;
            }
            return privateFields;
        };
    };

    TestClass.prototype.prototypeHello = function () {
        var privateFields = this.getPrivateFields(hash);
        privateFields.field1 = Math.round(Math.random() * 100);
        privateFields.field2 = Math.round(Math.random() * 100);
    };

    TestClass.prototype.logField1 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field1);
    };

    TestClass.prototype.logField2 = function () {
        var privateFields = this.getPrivateFields(hash);
        console.log(privateFields.field2);
    };

    return TestClass;
})();

Dies funktioniert so, dass eine Instanzfunktion "this.getPrivateFields" für den Zugriff auf das private Variablenobjekt "privateFields" bereitgestellt wird. Diese Funktion gibt jedoch nur das Objekt "privateFields" innerhalb des definierten Hauptabschlusses zurück (auch Prototypfunktionen, die "this.getPrivateFields" verwenden "müssen innerhalb dieses Verschlusses definiert werden).

Ein zur Laufzeit erzeugter und schwer zu erratender Hash wird als Parameter verwendet, um sicherzustellen, dass das Objekt "privateFields" auch dann nicht zurückgegeben wird, wenn "getPrivateFields" außerhalb des Schließungsbereichs aufgerufen wird.

Der Nachteil ist, dass wir TestClass nicht mit mehr Prototypfunktionen außerhalb des Verschlusses erweitern können.

Hier ist ein Testcode:

var t1 = new TestClass();
console.log('Initial t1 field1 is: ');
t1.logField1();
console.log('Initial t1 field2 is: ');
t1.logField2();
t1.prototypeHello();
console.log('t1 field1 is now: ');
t1.logField1();
console.log('t1 field2 is now: ');
t1.logField2();
var t2 = new TestClass();
console.log('Initial t2 field1 is: ');
t2.logField1();
console.log('Initial t2 field2 is: ');
t2.logField2();
t2.prototypeHello();
console.log('t2 field1 is now: ');
t2.logField1();
console.log('t2 field2 is now: ');
t2.logField2();

console.log('t1 field1 stays: ');
t1.logField1();
console.log('t1 field2 stays: ');
t1.logField2();

t1.getPrivateFields(11233);

BEARBEITEN: Mit dieser Methode ist es auch möglich, private Funktionen zu "definieren".

TestClass.prototype.privateFunction = function (hashed) {
    if(hashed !== hash) {
        throw "Cannot access private function.";
    }
};

TestClass.prototype.prototypeHello = function () {
    this.privateFunction(hash);
};
Jannes Botis
quelle
0

Ich habe heute damit herumgespielt und dies war die einzige Lösung, die ich finden konnte, ohne Symbole zu verwenden. Das Beste daran ist, dass eigentlich alles ganz privat sein kann.

Die Lösung basiert auf einem selbst entwickelten Modullader, der im Grunde genommen zum Vermittler für einen privaten Speichercache wird (unter Verwendung einer schwachen Karte).

   const loader = (function() {
        function ModuleLoader() {}

    //Static, accessible only if truly needed through obj.constructor.modules
    //Can also be made completely private by removing the ModuleLoader prefix.
    ModuleLoader.modulesLoaded = 0;
    ModuleLoader.modules = {}

    ModuleLoader.prototype.define = function(moduleName, dModule) {
        if (moduleName in ModuleLoader.modules) throw new Error('Error, duplicate module');

        const module = ModuleLoader.modules[moduleName] = {}

        module.context = {
            __moduleName: moduleName,
            exports: {}
        }

        //Weak map with instance as the key, when the created instance is garbage collected or goes out of scope this will be cleaned up.
        module._private = {
            private_sections: new WeakMap(),
            instances: []
        };

        function private(action, instance) {
            switch (action) {
                case "create":
                    if (module._private.private_sections.has(instance)) throw new Error('Cannot create private store twice on the same instance! check calls to create.')
                    module._private.instances.push(instance);
                    module._private.private_sections.set(instance, {});
                    break;
                case "delete":
                    const index = module._private.instances.indexOf(instance);
                    if (index == -1) throw new Error('Invalid state');
                    module._private.instances.slice(index, 1);
                    return module._private.private_sections.delete(instance);
                    break;
                case "get":
                    return module._private.private_sections.get(instance);
                    break;
                default:
                    throw new Error('Invalid action');
                    break;
            }
        }

        dModule.call(module.context, private);
        ModuleLoader.modulesLoaded++;
    }

    ModuleLoader.prototype.remove = function(moduleName) {
        if (!moduleName in (ModuleLoader.modules)) return;

        /*
            Clean up as best we can.
        */
        const module = ModuleLoader.modules[moduleName];
        module.context.__moduleName = null;
        module.context.exports = null;
        module.cotext = null;
        module._private.instances.forEach(function(instance) { module._private.private_sections.delete(instance) });
        for (let i = 0; i < module._private.instances.length; i++) {
            module._private.instances[i] = undefined;
        }
        module._private.instances = undefined;
        module._private = null;
        delete ModuleLoader.modules[moduleName];
        ModuleLoader.modulesLoaded -= 1;
    }


    ModuleLoader.prototype.require = function(moduleName) {
        if (!(moduleName in ModuleLoader.modules)) throw new Error('Module does not exist');

        return ModuleLoader.modules[moduleName].context.exports;
    }



     return new ModuleLoader();
    })();

    loader.define('MyModule', function(private_store) {
        function MyClass() {
            //Creates the private storage facility. Called once in constructor.
            private_store("create", this);


            //Retrieve the private storage object from the storage facility.
            private_store("get", this).no = 1;
        }

        MyClass.prototype.incrementPrivateVar = function() {
            private_store("get", this).no += 1;
        }

        MyClass.prototype.getPrivateVar = function() {
            return private_store("get", this).no;
        }

        this.exports = MyClass;
    })

    //Get whatever is exported from MyModule
    const MyClass = loader.require('MyModule');

    //Create a new instance of `MyClass`
    const myClass = new MyClass();

    //Create another instance of `MyClass`
    const myClass2 = new MyClass();

    //print out current private vars
    console.log('pVar = ' + myClass.getPrivateVar())
    console.log('pVar2 = ' + myClass2.getPrivateVar())

    //Increment it
    myClass.incrementPrivateVar()

    //Print out to see if one affected the other or shared
    console.log('pVar after increment = ' + myClass.getPrivateVar())
    console.log('pVar after increment on other class = ' + myClass2.getPrivateVar())

    //Clean up.
    loader.remove('MyModule')
Eladian
quelle
0

Ich weiß, dass es mehr als ein Jahrzehnt her ist, seit dies gefragt wurde, aber ich habe gerade zum n-ten Mal in meinem Programmiererleben darüber nachgedacht und eine mögliche Lösung gefunden, die ich noch nicht ganz mag . Ich habe diese Methode noch nicht dokumentiert gesehen, daher werde ich sie als "privates / öffentliches Dollar-Muster" oder _ $ / $ -Muster bezeichnen .

var ownFunctionResult = this.$("functionName"[, arg1[, arg2 ...]]);
var ownFieldValue = this._$("fieldName"[, newValue]);

var objectFunctionResult = objectX.$("functionName"[, arg1[, arg2 ...]]);

//Throws an exception. objectX._$ is not defined
var objectFieldValue = objectX._$("fieldName"[, newValue]);

Das Konzept verwendet eine ClassDefinition- Funktion, die eine Konstruktorfunktion zurückgibt, die ein Interface- Objekt zurückgibt . Die einzige Methode $der Schnittstelle besteht darin , ein nameArgument zu empfangen , um die entsprechende Funktion im Konstruktorobjekt aufzurufen, und alle zusätzlichen Argumente, die danach übergeben werdenname werden, werden im Aufruf übergeben.

Die global definierte Hilfsfunktion ClassValuesspeichert alle Felder in einem Objekt nach Bedarf. Es definiert die _$Funktion, über die auf sie zugegriffen werden soll name. Dies folgt einem kurzen get / set-Muster. Wenn valuees übergeben wird, wird es als neuer Variablenwert verwendet.

var ClassValues = function (values) {
  return {
    _$: function _$(name, value) {
      if (arguments.length > 1) {
        values[name] = value;
      }

      return values[name];
    }
  };
};

Die global definierte Funktion verwendet Interfaceein Objekt und ein ValuesObjekt, um eine _interfaceFunktion mit einer einzigen Funktion zurückzugeben $, die untersucht obj, ob eine nach dem Parameter benannte Funktion gefunden wurde , nameund sie valuesals Objekt mit Gültigkeitsbereich aufruft . Die an übergebenen zusätzlichen Argumente $werden beim Funktionsaufruf übergeben.

var Interface = function (obj, values, className) {
  var _interface = {
    $: function $(name) {
      if (typeof(obj[name]) === "function") {
        return obj[name].apply(values, Array.prototype.splice.call(arguments, 1));
      }

      throw className + "." + name + " is not a function.";
    }
  };

  //Give values access to the interface.
  values.$ = _interface.$;

  return _interface;
};

Im folgenden Beispiel ClassXwird das Ergebnis von zugeordnet ClassDefinition, welches die ConstructorFunktion ist. Constructorkann eine beliebige Anzahl von Argumenten erhalten. Interfaceist, was externer Code nach dem Aufrufen des Konstruktors erhält.

var ClassX = (function ClassDefinition () {
  var Constructor = function Constructor (valA) {
    return Interface(this, ClassValues({ valA: valA }), "ClassX");
  };

  Constructor.prototype.getValA = function getValA() {
    //private value access pattern to get current value.
    return this._$("valA");
  };

  Constructor.prototype.setValA = function setValA(valA) {
    //private value access pattern to set new value.
    this._$("valA", valA);
  };

  Constructor.prototype.isValAValid = function isValAValid(validMessage, invalidMessage) {
    //interface access pattern to call object function.
    var valA = this.$("getValA");

    //timesAccessed was not defined in constructor but can be added later...
    var timesAccessed = this._$("timesAccessed");

    if (timesAccessed) {
      timesAccessed = timesAccessed + 1;
    } else {
      timesAccessed = 1;
    }

    this._$("timesAccessed", timesAccessed);

    if (valA) {
      return "valA is " + validMessage + ".";
    }

    return "valA is " + invalidMessage + ".";
  };

  return Constructor;
}());

Es macht keinen Sinn, nicht prototypisierte Funktionen zu haben Constructor, obwohl Sie sie im Konstruktorfunktionskörper definieren könnten. Alle Funktionen werden mit dem öffentlichen Dollar-Muster aufgerufen this.$("functionName"[, param1[, param2 ...]]). Auf die privaten Werte wird mit dem privaten Dollarmuster zugegriffen this._$("valueName"[, replacingValue]);. Da Interfacees keine Definition für gibt _$, können externe Objekte nicht auf die Werte zugreifen. Da jeder Prototyp eines Funktionskörpers thisauf das valuesObjekt in der Funktion festgelegt ist $, erhalten Sie Ausnahmen, wenn Sie Constructor-Geschwisterfunktionen direkt aufrufen. Das _ $ / $ -Muster muss auch in prototypisierten Funktionskörpern befolgt werden. Unten Beispielverwendung.

var classX1 = new ClassX();
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
console.log("classX1.valA: " + classX1.$("getValA"));
classX1.$("setValA", "v1");
console.log("classX1." + classX1.$("isValAValid", "valid", "invalid"));
var classX2 = new ClassX("v2");
console.log("classX1.valA: " + classX1.$("getValA"));
console.log("classX2.valA: " + classX2.$("getValA"));
//This will throw an exception
//classX1._$("valA");

Und die Konsolenausgabe.

classX1.valA is invalid.
classX1.valA: undefined
classX1.valA is valid.
classX1.valA: v1
classX2.valA: v2

Das _ $ / $ -Muster ermöglicht die vollständige Vertraulichkeit von Werten in Klassen mit vollständigem Prototyp. Ich weiß nicht, ob ich das jemals benutzen werde oder ob es Fehler hat, aber hey, es war ein gutes Rätsel!

JPortillo
quelle
0

ES6 WeakMaps

Durch die Verwendung eines einfachen Musters in ES6 WeakMaps können private Mitgliedsvariablen abgerufen werden, die über die Prototypfunktionen erreichbar sind .

Hinweis: Die Verwendung von WeakMaps garantiert die Sicherheit gegen Speicherlecks , indem der Garbage Collector nicht verwendete Instanzen identifizieren und verwerfen kann.

// Create a private scope using an Immediately 
// Invoked Function Expression...
let Person = (function() {

    // Create the WeakMap that will hold each  
    // Instance collection's of private data
    let privateData = new WeakMap();
    
    // Declare the Constructor :
    function Person(name) {
        // Insert the private data in the WeakMap,
        // using 'this' as a unique acces Key
        privateData.set(this, { name: name });
    }
    
    // Declare a prototype method 
    Person.prototype.getName = function() {
        // Because 'privateData' is in the same 
        // scope, it's contents can be retrieved...
        // by using  again 'this' , as  the acces key 
        return privateData.get(this).name;
    };

    // return the Constructor
    return Person;
}());

Eine detailliertere Erklärung dieses Musters finden Sie hier

colxi
quelle
-1

Sie müssen 3 Dinge in Ihrem Code ändern:

  1. Ersetzen var privateField = "hello"durch this.privateField = "hello".
  2. Im Prototyp ersetzen privateFielddurch this.privateField.
  3. Im Nicht-Prototyp ersetzen auch privateFieldmit this.privateField.

Der endgültige Code wäre der folgende:

TestClass = function(){
    this.privateField = "hello";
    this.nonProtoHello = function(){alert(this.privateField)};
}

TestClass.prototype.prototypeHello = function(){alert(this.privateField)};

var t = new TestClass();

t.prototypeHello()
A-Sharabiani
quelle
this.privateFieldwäre kein privates Feld. es ist von außen zugänglich:t.privateField
V. Rubinetti
-2

Sie können eine Prototypzuweisung innerhalb der Konstruktordefinition verwenden.

Die Variable ist für die vom Prototyp hinzugefügte Methode sichtbar, aber alle Instanzen der Funktionen greifen auf dieselbe SHARED-Variable zu.

function A()
{
  var sharedVar = 0;
  this.local = "";

  A.prototype.increment = function(lval)
  {    
    if (lval) this.local = lval;    
    alert((++sharedVar) + " while this.p is still " + this.local);
  }
}

var a = new A();
var b = new A();    
a.increment("I belong to a");
b.increment("I belong to b");
a.increment();
b.increment();

Ich hoffe das kann nützlich sein.

AngelsCrimes
quelle