web-dev-qa-db-de.com

Private Eigenschaften in JavaScript ES6-Klassen

Ist es möglich, private Eigenschaften in ES6-Klassen zu erstellen?

Hier ein Beispiel ... Wie kann ich den Zugriff auf instance.property verhindern?

class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
362
d13

Private Felder werden im ECMA-Standard implementiert. Sie können sie heute mit babel 7 und Stage 3 Preset verwenden. Siehe babel REPL Beispiel .

class Something {
  #property;

  constructor(){
    this.#property = "test";
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
67
Alister Norris

Kurze Antwort, nein, es gibt keine native Unterstützung für private Immobilien mit ES6-Klassen.

Sie können dieses Verhalten jedoch nachahmen, indem Sie die neuen Eigenschaften nicht an das Objekt anhängen, sondern sie innerhalb eines Klassenkonstruktors belassen und mithilfe von Gettern und Setters die verborgenen Eigenschaften erreichen. Beachten Sie, dass die Getter und Setter in jeder neuen Instanz der Klasse neu definiert werden.

ES6

class Person {
    constructor(name) {
        var _name = name
        this.setName = function(name) { _name = name; }
        this.getName = function() { return _name; }
    }
}

ES5

function Person(name) {
    var _name = name
    this.setName = function(name) { _name = name; }
    this.getName = function() { return _name; }
}
228
MetalGodwin

Um die Antwort von @ loganfsmyth zu erweitern:

Die einzigen wirklich privaten Daten in JavaScript sind noch Variablen im Gültigkeitsbereich. Sie können keine privaten Eigenschaften im Sinne von Eigenschaften haben, auf die intern wie öffentliche Eigenschaften zugegriffen wird. Sie können jedoch Bereichsvariablen verwenden, um private Daten zu speichern.

Bereichsvariablen

Der Ansatz hier ist, den Umfang der Konstruktorfunktion, die privat ist, zum Speichern privater Daten zu verwenden. Damit Methoden auf diese privaten Daten zugreifen können, müssen sie auch im Konstruktor erstellt werden. Das bedeutet, dass Sie sie mit jeder Instanz neu erstellen. Dies ist eine Leistungs- und Speicherstrafe, aber einige glauben, dass die Strafe akzeptabel ist. Die Strafe kann für Methoden vermieden werden, die keinen Zugriff auf private Daten benötigen, indem sie wie üblich zum Prototyp hinzugefügt werden.

Beispiel:

function Person(name) {
  let age = 20; // this is private
  this.name = name; // this is public

  this.greet = function () {
    // here we can access both name and age
    console.log(`name: ${this.name}, age: ${age}`);
  };
}

let joe = new Person('Joe');
joe.greet();

// here we can access name but not age

Umfangreiche WeakMap

Eine WeakMap kann verwendet werden, um die Leistung und den Speichernachteil des vorherigen Ansatzes zu vermeiden. WeakMaps verknüpfen Daten mit Objekten (hier Instanzen) auf eine solche Weise, dass nur mit dieser WeakMap auf sie zugegriffen werden kann. Daher verwenden wir die Methode der Bereichsvariablen, um eine private WeakMap zu erstellen, und dann diese WeakMap, um private Daten abzurufen, die this zugeordnet sind. Dies ist schneller als die Variablenvariablenmethode, da alle Ihre Instanzen eine einzelne WeakMap gemeinsam nutzen können. Sie müssen also keine Methoden neu erstellen, um nur auf ihre eigenen WeakMaps zugreifen zu können.

Beispiel:

let Person = (function () {
  let privateProps = new WeakMap();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      privateProps.set(this, {age: 20}); // this is private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${privateProps.get(this).age}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// here we can access joe's name but not age

In diesem Beispiel wird ein Objekt verwendet, um eine WeakMap für mehrere private Eigenschaften zu verwenden. Sie können auch mehrere WeakMaps verwenden und wie age.set(this, 20) verwenden, oder einen kleinen Wrapper schreiben und ihn auf andere Weise verwenden, wie privateProps.set(this, 'age', 0).

Die Vertraulichkeit dieses Ansatzes könnte theoretisch durch Manipulation des globalen WeakMap-Objekts verletzt werden. Das heißt, alle JavaScript können durch verstümmelte Globals gebrochen werden. Unser Code basiert bereits auf der Annahme, dass dies nicht geschieht.

(Diese Methode kann auch mit Map ausgeführt werden, aber WeakMap ist besser, da Map Speicherverluste verursacht, wenn Sie nicht sehr vorsichtig sind und zu diesem Zweck die beiden nicht anders sind.)

Halbe Antwort: Symbolsymbole

Ein Symbol ist ein Typ von Grundwerten, der als Eigenschaftsname dienen kann. Sie können die Bereichsvariablenmethode verwenden, um ein privates Symbol zu erstellen und dann private Daten unter this[mySymbol] zu speichern.

Die Vertraulichkeit dieser Methode kann mit Object.getOwnPropertySymbols verletzt werden, ist aber etwas umständlich.

Beispiel:

let Person = (function () {
  let ageKey = Symbol();

  class Person {
    constructor(name) {
      this.name = name; // this is public
      this[ageKey] = 20; // this is intended to be private
    }

    greet() {
      // Here we can access both name and age
      console.log(`name: ${this.name}, age: ${this[ageKey]}`);
    }
  }

  return Person;
})();

let joe = new Person('Joe');
joe.greet();

// Here we can access joe's name and, with a little effort, age. ageKey is
// not in scope, but we can obtain it by listing all Symbol properties on
// joe with `Object.getOwnPropertySymbols(joe)`.

Half-Answer: Unterstriche

Bei der alten Standardeinstellung verwenden Sie einfach eine öffentliche Eigenschaft mit einem Unterstrich-Präfix. Obwohl dies in keiner Weise ein privates Eigentum ist, ist diese Konvention so weit verbreitet, dass sie gute Arbeit leistet, indem sie mitteilt, dass der Leser das Eigentum als privat behandeln sollte, was die Arbeit oft erledigt. Im Gegenzug für dieses Versäumnis erhalten wir einen Ansatz, der einfacher zu lesen, einfacher zu schreiben und schneller ist.

Beispiel:

class Person {
  constructor(name) {
    this.name = name; // this is public
    this._age = 20; // this is intended to be private
  }

  greet() {
    // Here we can access both name and age
    console.log(`name: ${this.name}, age: ${this._age}`);
  }
}

let joe = new Person('Joe');
joe.greet();

// Here we can access both joe's name and age. But we know we aren't
// supposed to access his age, which just might stop us.

Fazit

Ab ES2017 gibt es noch keine perfekte Möglichkeit, private Immobilien zu betreiben. Verschiedene Ansätze haben Vor- und Nachteile. Bereichsvariablen sind wirklich privat; Bereichs-WeakMaps sind sehr privat und praktischer als Bereichsvariablen. Symbolsymbole sind recht privat und praktisch praktisch; Unterstriche sind oft privat und sehr praktisch.

159
twhb

Update: Ein Vorschlag mit einer schöneren Syntax ist unterwegs. Beiträge sind willkommen.


Ja, es gibt - für den beschränkten Zugriff auf Objekte - ES6 führt Symbols ein.

Symbole sind einzigartig, Sie können von außen keinen Zugriff auf ein Symbol erhalten, außer durch Reflektion (wie bei Privaten in Java/C #). Jeder, der Zugriff auf ein Symbol im Inneren hat, kann es für den Schlüsselzugriff verwenden:

var property = Symbol();
class Something {
    constructor(){
        this[property] = "test";
    }
}

var instance = new Something();

console.log(instance.property); //=> undefined, can only access with access to the Symbol
111

Die Antwort ist nein". Sie können jedoch einen privaten Zugriff auf folgende Eigenschaften erstellen:

(Der Vorschlag, dass Symbole verwendet werden könnten, um den Datenschutz sicherzustellen, war in einer früheren Version der ES6-Spezifikation wahr, trifft jedoch nicht mehr zu: https://mail.mozilla.org/pipermail/es-discuss/2014-January/ 035604.html und https://stackoverflow.com/a/22280202/1282216 Für eine längere Diskussion über Symbole und Datenschutz siehe: https://curiosity-driven.org/private-properties- in-javascript )

28
d13

Der einzige Weg, um echte Privatsphäre in JS zu erhalten, ist das Scoping. Daher gibt es keine Möglichkeit, über eine Eigenschaft zu verfügen, die ein Mitglied von this ist und auf die nur innerhalb der Komponente zugegriffen werden kann. Um wirklich private Daten in ES6 zu speichern, verwenden Sie eine WeakMap.

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    privateProp1.set(this, "I am Private1");
    privateProp2.set(this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(privateProp1.get(this), privateProp2.get(this))
    };        
  }

  printPrivate() {
    console.log(privateProp1.get(this));
  }
}

Offensichtlich ist dies wahrscheinlich langsam und definitiv hässlich, aber es bietet Privatsphäre.

Denken Sie daran, dass EVEN THIS nicht perfekt ist, weil Javascript so dynamisch ist. Jemand könnte noch etwas tun

var oldSet = WeakMap.prototype.set;
WeakMap.prototype.set = function(key, value){
    // Store 'this', 'key', and 'value'
    return oldSet.call(this, key, value);
};

wenn Sie die Werte beim Speichern abfangen möchten, müssen Sie einen lokalen Verweis auf .set und .get aufzeichnen, der explizit verwendet werden soll, anstatt sich auf den überschreibbaren Prototyp zu verlassen.

const {set: WMSet, get: WMGet} = WeakMap.prototype;

const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();

class SomeClass {
  constructor() {
    WMSet.call(privateProp1, this, "I am Private1");
    WMSet.call(privateProp2, this, "I am Private2");

    this.publicVar = "I am public";
    this.publicMethod = () => {
      console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
    };        
  }

  printPrivate() {
    console.log(WMGet.call(privateProp1, this));
  }
}
26
loganfsmyth

Zur späteren Bezugnahme auf andere Betrachter höre ich jetzt, dass es empfohlen wird, WeakMaps zu verwenden, um private Daten zu speichern.

Hier ist ein klareres Arbeitsbeispiel:

function storePrivateProperties(a, b, c, d) {
  let privateData = new WeakMap;
  // unique object as key, weak map can only accept object as key, when key is no longer referened, garbage collector claims the key-value 
  let keyA = {}, keyB = {}, keyC = {}, keyD = {};

  privateData.set(keyA, a);
  privateData.set(keyB, b);
  privateData.set(keyC, c);
  privateData.set(keyD, d);

  return {
    logPrivateKey(key) {
      switch(key) {
      case "a":
        console.log(privateData.get(keyA));
        break;
      case "b":
        console.log(privateData.get(keyB));
        break;
      case "c":
        console.log(privateData.get(keyC));
        break;
      case "d":
        console.log(privateData.set(keyD));
        break;
      default:
        console.log(`There is no value for ${key}`)
      }
    }
  }
}
21
user1076821

Kommt drauf an wen Sie fragen :-)

Im Maximally Minimal-Klassen - Vorschlag ist kein private-Eigenschaftsmodifikator enthalten der scheinbar in den aktuellen Entwurf aufgenommen wurde.

Es kann jedoch eine Unterstützung fürprivate names geben, die private Eigenschaften zulässt - und sie könnten wahrscheinlich auch in Klassendefinitionen verwendet werden.

11
Bergi

Fertigstellung von @ d13 und Kommentaren von @ johnny-oshika und @DanyalAytekin:

In dem von @ johnny-oshika bereitgestellten Beispiel könnten wir normale Funktionen anstelle von Pfeilfunktionen verwenden und dann .bind mit dem aktuellen Objekt plus einem _privates-Objekt als Curry-Parameter:

etwas.js

function _greet(_privates) {
  return 'Hello ' + _privates.message;
}

function _updateMessage(_privates, newMessage) {
  _privates.message = newMessage;
}

export default class Something {
  constructor(message) {
    const _privates = {
      message
    };

    this.say = _greet.bind(this, _privates);
    this.updateMessage = _updateMessage.bind(this, _privates);
  }
}

main.js

import Something from './something.js';

const something = new Something('Sunny day!');

const message1 = something.say();
something.updateMessage('Cloudy day!');
const message2 = something.say();

console.log(message1 === 'Hello Sunny day!');  // true
console.log(message2 === 'Hello Cloudy day!');  // true

// the followings are not public
console.log(something._greet === undefined);  // true
console.log(something._privates === undefined);  // true
console.log(something._updateMessage === undefined);  // true

// another instance which doesn't share the _privates
const something2 = new Something('another Sunny day!');

const message3 = something2.say();

console.log(message3 === 'Hello another Sunny day!'); // true

Vorteile, die mir einfallen: 

  • wir können private Methoden haben (_greet und _updateMessage verhalten sich wie private Methoden, solange wir die Referenzen nicht export verwenden) 
  • obwohl es sich nicht um einen Prototyp handelt, sparen die oben genannten Methoden Speicher, da die Instanzen einmal außerhalb der Klasse erstellt werden (anstatt sie im Konstruktor zu definieren). 
  • wir verlieren keine Globals, da wir uns in einem Modul befinden 
  • wir können auch private Eigenschaften mit dem gebundenen _privates-Objekt haben

Einige Nachteile die ich mir vorstellen kann: 

Ein laufendes Snippet ist hier zu finden: http://www.webpackbin.com/NJgI5J8lZ

9
efidiles

Ja - Sie können eine gekapselte Eigenschaft erstellen. Dies wurde jedoch nicht mit Zugriffsmodifizierern (public | private) vorgenommen, zumindest nicht mit ES6.

Hier ein einfaches Beispiel, wie es mit ES6 gemacht werden kann:

1 Erstellen Sie eine Klasse mit class Word

2 Innerhalb des Konstruktors deklarieren Sie die Blockbereichsvariable mit let OR const Reservierte Wörter ->, da es sich um Blockbereiche handelt, auf die kein Zugriff von außen möglich ist (gekapselt).

3 Um diesen Variablen Zugriff auf die Zugriffssteuerung (Setter | Getters) zu gewähren, können Sie die Instanzmethode innerhalb ihres Konstruktors deklarieren, indem Sie die this.methodName=function(){}-Syntax verwenden 

"use strict";
    class Something{
        constructor(){
            //private property
            let property="test";
            //private final (immutable) property
            const property2="test2";
            //public getter
            this.getProperty2=function(){
                return property2;
            }
            //public getter
            this.getProperty=function(){
                return property;
            }
            //public setter
            this.setProperty=function(prop){
                property=prop;
            }
        }
    }

Jetzt können wir es überprüfen:

var s=new Something();
    console.log(typeof s.property);//undefined 
    s.setProperty("another");//set to encapsulated `property`
    console.log(s.getProperty());//get encapsulated `property` value
    console.log(s.getProperty2());//get encapsulated immutable `property2` value
8
Nikita Kurtin

Die Verwendung von ES6-Modulen (ursprünglich von @ d13 vorgeschlagen) funktioniert für mich gut. Private Immobilien werden nicht perfekt nachgeahmt, aber Sie können zumindest sicher sein, dass Eigenschaften, die privat sein sollten, nicht außerhalb Ihrer Klasse laufen. Hier ist ein Beispiel:

etwas.js

let _message = null;
const _greet = name => {
  console.log('Hello ' + name);
};

export default class Something {
  constructor(message) {
    _message = message;
  }

  say() {
    console.log(_message);
    _greet('Bob');
  }
};

Dann kann der verbrauchende Code folgendermaßen aussehen:

import Something from './something.js';

const something = new Something('Sunny day!');
something.say();
something._message; // undefined
something._greet(); // exception

Update (wichtig):

Wie in den Kommentaren von @DanyalAytekin beschrieben, sind diese privaten Eigenschaften statisch, also global. Sie funktionieren gut, wenn Sie mit Singletons arbeiten, aber auf transiente Objekte muss geachtet werden. Das obige Beispiel erweitern:

import Something from './something.js';
import Something2 from './something.js';

const a = new Something('a');
a.say(); // a

const b = new Something('b');
b.say(); // b

const c = new Something2('c');
c.say(); // c

a.say(); // c
b.say(); // c
c.say(); // c
8
Johnny Oshika

Ein anderer Ansatz für "privat"

Anstatt gegen die Tatsache vorzugehen, dass private Sichtbarkeit in ES6 derzeit nicht verfügbar ist, habe ich mich für einen praktischeren Ansatz entschieden, der gut funktioniert, wenn Ihre IDE JSDoc (z. B. Webstorm) unterstützt. Die Idee ist die Verwendung des @private-Tags . In Bezug auf die Entwicklung hindert die IDE Sie daran, auf private Mitglieder von außerhalb ihrer Klasse zuzugreifen. Funktioniert ziemlich gut für mich und es ist wirklich nützlich, um interne Methoden auszublenden. Die Funktion zum automatischen Vervollständigen zeigt mir, was die Klasse wirklich zeigen wollte. Hier ist ein Beispiel:

 auto-complete showing just public stuff

6
Lucio Paiva

WeakMap

  • wird von IE11 unterstützt (Symbole sind nicht)
  • hard-Private (Requisiten, die Symbole verwenden, sind aufgrund von Object.getOwnPropertySymbols Soft-Private)
  • kann sehr sauber aussehen (im Gegensatz zu Verschlüssen, die alle Requisiten und Methoden im Konstruktor erfordern)

Definieren Sie zunächst eine Funktion zum Umbrechen von WeakMap:

function Private() {
  const map = new WeakMap();
  return obj => {
    let props = map.get(obj);
    if (!props) {
      props = {};
      map.set(obj, props);
    }
    return props;
  };
}

Erstellen Sie dann eine Referenz außerhalb Ihrer Klasse:

const p = new Private();

class Person {
  constructor(name, age) {
    this.name = name;
    p(this).age = age; // it's easy to set a private variable
  }

  getAge() {
    return p(this).age; // and get a private variable
  }
}

Hinweis: class wird nicht von IE11 unterstützt, im Beispiel sieht es jedoch sauberer aus.

6
kevlened

Ich denke, Benjamins Antwort ist wahrscheinlich für die meisten Fälle die beste Lösung, bis die Sprache explizit private Variablen unterstützt.

Wenn Sie jedoch aus irgendeinem Grund den Zugriff mit Object.getOwnPropertySymbols() verhindern müssen, muss eine von mir in Betracht gezogene Methode eine eindeutige, nicht konfigurierbare, nicht aufzählbare, nicht beschreibbare Eigenschaft enthalten, die als Eigenschafts-ID verwendet werden kann zu jedem Objekt in der Konstruktion (z. B. eine eindeutige Symbol, wenn Sie noch keine andere eindeutige Eigenschaft wie eine id haben). Behalten Sie dann einfach eine Zuordnung der "privaten" Variablen jedes Objekts mit diesem Bezeichner.

const privateVars = {};

class Something {
    constructor(){
        Object.defineProperty(this, '_sym', {
            configurable: false,
            enumerable: false,
            writable: false,
            value: Symbol()
        });

        var myPrivateVars = {
            privateProperty: "I'm hidden"
        };

        privateVars[this._sym] = myPrivateVars;

        this.property = "I'm public";
    }

    getPrivateProperty() {
        return privateVars[this._sym].privateProperty;
    }

    // A clean up method of some kind is necessary since the
    // variables won't be cleaned up from memory automatically
    // when the object is garbage collected
    destroy() {
        delete privateVars[this._sym];
    }
}

var instance = new Something();
console.log(instance.property); //=> "I'm public"
console.log(instance.privateProperty); //=> undefined
console.log(instance.getPrivateProperty()); //=> "I'm hidden"

Der potenzielle Vorteil dieses Ansatzes gegenüber der Verwendung einer WeakMap ist eine schnellere Zugriffszeit wenn die Leistung zum Problem wird.

4
NanoWizard

Ich persönlich mag den Vorschlag des bind-Operators:: und würde ihn dann mit der erwähnten Lösung @d13 kombinieren. Bleib aber vorerst bei @d13s Antwort, wo du das export-Schlüsselwort für deine Klasse verwendest und die privaten Funktionen einfügst das Modul.

es gibt noch eine weitere schwierige Lösung, die hier nicht erwähnt wurde. Im Folgenden handelt es sich um einen funktionaleren Ansatz, der es ermöglichen würde, alle privaten Requisiten/Methoden innerhalb der Klasse zu haben.

Private.js

export const get = state => key => state[key];
export const set = state => (key,value) => { state[key] = value; }

Test.js

import { get, set } from './utils/Private'
export default class Test {
  constructor(initialState = {}) {
    const _set = this.set = set(initialState);
    const _get = this.get = get(initialState);

    this.set('privateMethod', () => _get('propValue'));
  }

  showProp() {
    return this.get('privateMethod')();
  }
}

let one = new Test({ propValue: 5});
let two = new Test({ propValue: 8});
two.showProp(); // 8
one.showProp(); // 5

kommentare dazu wären dankbar.

4
Robin F.

Ich glaube, es ist möglich, das Beste aus beiden Welten zu erhalten, indem Sie innerhalb von Konstruktoren Verschlüsse verwenden. Es gibt zwei Varianten:

Alle Datenmitglieder sind privat

function myFunc() {
   console.log('Value of x: ' + this.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   console.log('Enhanced value of x: ' + (this.x + 1));
}

class Test {
   constructor() {

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(internal);
      
      this.myFunc = myFunc.bind(internal);
   }
};

Einige Mitglieder sind privat

HINWEIS: Dies ist zugegebenermaßen hässlich. Wenn Sie eine bessere Lösung kennen, bearbeiten Sie diese Antwort.

function myFunc(priv, pub) {
   pub.y = 3; // The Test object now gets a member 'y' with value 3.
   console.log('Value of x: ' + priv.x);
   this.myPrivateFunc();
}

function myPrivateFunc() {
   pub.z = 5; // The Test object now gets a member 'z' with value 3.
   console.log('Enhanced value of x: ' + (priv.x + 1));
}

class Test {
   constructor() {
      
      let self = this;

      let internal = {
         x : 2,
      };
      
      internal.myPrivateFunc = myPrivateFunc.bind(null, internal, self);
      
      this.myFunc = myFunc.bind(null, internal, self);
   }
};

4
JSInitiate

In der Tat ist es möglich, Symbole und Proxies zu verwenden. Sie verwenden die Symbole im Klassenbereich und setzen zwei Traps in einem Proxy: eine für den Prototyp der Klasse, sodass die Reflect.ownKeys (Instanz) oder Object.getOwnPropertySymbols Ihre Symbole nicht verraten, die andere für den Konstruktor selbst Wenn also new ClassName(attrs) aufgerufen wird, wird die zurückgegebene Instanz abgefangen und die eigenen Eigenschaftssymbole werden blockiert .. _ Hier ist der Code:

const Human = (function() {
  const pet = Symbol();
  const greet = Symbol();

  const Human = privatizeSymbolsInFn(function(name) {
    this.name = name; // public
    this[pet] = 'dog'; // private 
  });

  Human.prototype = privatizeSymbolsInObj({
    [greet]() { // private
      return 'Hi there!';
    },
    revealSecrets() {
      console.log(this[greet]() + ` The pet is a ${this[pet]}`);
    }
  });

  return Human;
})();

const bob = new Human('Bob');

console.assert(bob instanceof Human);
console.assert(Reflect.ownKeys(bob).length === 1) // only ['name']
console.assert(Reflect.ownKeys(Human.prototype).length === 1 ) // only ['revealSecrets']


// Setting up the traps inside proxies:
function privatizeSymbolsInObj(target) { 
  return new Proxy(target, { ownKeys: Object.getOwnPropertyNames });
}

function privatizeSymbolsInFn(Class) {
  function construct(TargetClass, argsList) {
    const instance = new TargetClass(...argsList);
    return privatizeSymbolsInObj(instance);
  }
  return new Proxy(Class, { construct });
}

Reflect.ownKeys() funktioniert so: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj)) Deshalb brauchen wir eine Falle für diese Objekte.

4
Francisco Neto

Ich bin auf diesen Beitrag gestoßen, als ich nach den besten Methoden für "private Daten für Klassen" gesucht habe. Es wurde erwähnt, dass einige der Muster Leistungsprobleme haben würden.

Ich habe ein paar Jsperf-Tests zusammengestellt, die auf den 4 Hauptmustern des Online-Buches "Exploring ES6" basieren:

http://exploringjs.com/es6/ch_classes.html#sec_private-data-for-classes

Die Tests finden Sie hier:

https://jsperf.com/private-data-for-classes

In Chrome 63.0.3239/Mac OS X 10.11.6 waren die besten Muster "Private Daten über Konstruktorumgebungen" und "Private Daten über eine Namenskonvention". Für mich lief Safari für WeakMap gut, aber Chrome nicht so gut.

Ich kenne die Auswirkungen auf den Speicher nicht, aber das Muster für "Konstruktorumgebungen", von dem einige gewarnt hatten, wäre ein Leistungsproblem, das sehr leistungsfähig war.

Die 4 Grundmuster sind:

Private Daten über Konstruktorumgebungen

class Countdown {
    constructor(counter, action) {
        Object.assign(this, {
            dec() {
                if (counter < 1) return;
                counter--;
                if (counter === 0) {
                    action();
                }
            }
        });
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private Daten über Konstruktorumgebungen 2

class Countdown {
    constructor(counter, action) {
        this.dec = function dec() {
            if (counter < 1) return;
            counter--;
            if (counter === 0) {
                action();
            }
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private Daten über eine Namenskonvention

class Countdown {
    constructor(counter, action) {
        this._counter = counter;
        this._action = action;
    }
    dec() {
        if (this._counter < 1) return;
        this._counter--;
        if (this._counter === 0) {
            this._action();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private Daten über WeakMaps

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
    constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
    }
    dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
            _action.get(this)();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();

Private Daten über Symbole

const _counter = Symbol('counter');
const _action = Symbol('action');

class Countdown {
    constructor(counter, action) {
        this[_counter] = counter;
        this[_action] = action;
    }
    dec() {
        if (this[_counter] < 1) return;
        this[_counter]--;
        if (this[_counter] === 0) {
            this[_action]();
        }
    }
}
const c = new Countdown(2, () => {});
c.dec();
c.dec();
3
MarkM
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
instance.property = "can read from outside, but can't write";
console.log(instance.property); //=> "test"
3
Ilya Zarembsky

Sogar TypeScript kann das nicht. Aus ihrer Dokumentation

Wenn ein Mitglied als privat markiert ist, kann nicht von außerhalb seiner Klasse aus auf ihn zugegriffen werden. Zum Beispiel:

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // Error: 'name' is private;

Aber auf ihrem Spielplatz das gibt:

var Animal = (function () {
    function Animal(theName) {
        this.name = theName;
    }
    return Animal;
}());
console.log(new Animal("Cat").name);

Ihr "privates" Schlüsselwort ist also unwirksam.

3
Michael Franzl

Ich komme sehr spät zu dieser Gruppe, aber ich habe die OP-Frage bei einer Suche getroffen, also ...Ja, Sie können private Eigenschaften haben, indem Sie die Klassendeklaration in eine Schließung einschließen.

Es gibt ein Beispiel, wie ich private Methoden in this codepen habe. Im folgenden Snippet verfügt die Subscribable-Klasse über zwei "private" Funktionen process und processCallbacks. Alle Eigenschaften können auf diese Weise hinzugefügt werden und werden durch die Verwendung des Verschlusses geheim gehalten. IMO-Datenschutz ist ein seltenes Bedürfnis, wenn Bedenken gut voneinander getrennt sind und Javascript nicht durch Hinzufügen von mehr Syntax aufgebläht werden muss, wenn eine Schließung die Aufgabe ordentlich erledigt.

const Subscribable = (function(){

  const process = (self, eventName, args) => {
    self.processing.set(eventName, setTimeout(() => processCallbacks(self, eventName, args)))};

  const processCallbacks = (self, eventName, args) => {
    if (self.callingBack.get(eventName).length > 0){
      const [nextCallback, ...callingBack] = self.callingBack.get(eventName);
      self.callingBack.set(eventName, callingBack);
      process(self, eventName, args);
      nextCallback(...args)}
    else {
      delete self.processing.delete(eventName)}};

  return class {
    constructor(){
      this.callingBack = new Map();
      this.processing = new Map();
      this.toCallbacks = new Map()}

    subscribe(eventName, callback){
      const callbacks = this.unsubscribe(eventName, callback);
      this.toCallbacks.set(eventName,  [...callbacks, callback]);
      return () => this.unsubscribe(eventName, callback)}  // callable to unsubscribe for convenience

    unsubscribe(eventName, callback){
      let callbacks = this.toCallbacks.get(eventName) || [];
      callbacks = callbacks.filter(subscribedCallback => subscribedCallback !== callback);
      if (callbacks.length > 0) {
        this.toCallbacks.set(eventName, callbacks)}
      else {
        this.toCallbacks.delete(eventName)}
      return callbacks}

    emit(eventName, ...args){
      this.callingBack.set(eventName, this.toCallbacks.get(eventName) || []);
      if (!this.processing.has(eventName)){
        process(this, eventName, args)}}}})();

Ich mag diesen Ansatz, weil er die Anliegen gut voneinander trennt und die Dinge wirklich privat hält. Der einzige Nachteil ist die Notwendigkeit, "Selbst" (oder etwas Ähnliches) zu verwenden, um in privaten Inhalten auf "dieses" zu verweisen.

3
Paul Whipp

Ich habe eine sehr einfache Lösung gefunden, benutze einfach Object.freeze(). Das Problem ist natürlich, dass Sie dem Objekt später nichts hinzufügen können.

class Cat {
    constructor(name ,age) {
        this.name = name
        this.age = age
        Object.freeze(this)
    }
}

let cat = new Cat('Garfield', 5)
cat.age = 6 // doesn't work, even throws an error in strict mode
2
Nikola Andreev

Siehe diese Antwort für eine saubere und einfache "Klassen" -Lösung mit einer privaten und öffentlichen Schnittstelle und Unterstützung für die Komposition

2
kofifus

Ja, und das ganz leicht. Dazu legen Sie Ihre privaten Variablen und Funktionen offen, indem Sie den Prototypobjektgraph im Konstruktor zurückgeben. Dies ist nichts Neues, aber nehmen Sie sich ein bisschen Foo, um die Eleganz zu verstehen. Auf diese Weise werden keine globalen Bereiche oder Schwachmaps verwendet. Es ist eine Form der Reflexion, die in die Sprache eingebaut ist. Abhängig davon, wie Sie dies nutzen; Man kann entweder eine Ausnahme erzwingen, die den Aufrufstapel unterbricht, oder die Ausnahme als undefined begraben. Dies wird im Folgenden erläutert und Sie können mehr über diese Funktionen lesen hier

class Clazz {
  constructor() {
    var _level = 1

    function _private(x) {
      return _level * x;
    }
    return {
      level: _level,
      public: this.private,
      public2: function(x) {
        return _private(x);
      },
      public3: function(x) {
        return _private(x) * this.public(x);
      },
    };
  }

  private(x) {
    return x * x;
  }
}

var clazz = new Clazz();

console.log(clazz._level); //undefined
console.log(clazz._private); // undefined
console.log(clazz.level); // 1
console.log(clazz.public(1)); //2
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error

2
1-14x0r

Ich benutze dieses Muster und es hat immer für mich funktioniert

class Test {
    constructor(data) {
        class Public {
            constructor(prv) {

                // public function (must be in constructor on order to access "prv" variable)
                connectToDb(ip) {
                    prv._db(ip, prv._err);
                } 
            }

            // public function w/o access to "prv" variable
            log() {
                console.log("I'm logging");
            }
        }

        // private variables
        this._data = data;
        this._err = function(ip) {
            console.log("could not connect to "+ip);
        }
    }

    // private function
    _db(ip, err) {
        if(!!ip) {
		    console.log("connected to "+ip+", sending data '"+this.data+"'");
			return true;
		}
        else err(ip);
    }
}



var test = new Test(10),
		ip = "185.167.210.49";
test.connectToDb(ip); // true
test.log(); // I'm logging
test._err(ip); // undefined
test._db(ip, function() { console.log("You have got hacked!"); }); // undefined

2
Yami Teru

Eigentlich ist es ist möglich.
1. Erstellen Sie zunächst die Klasse und geben Sie im Konstruktor die aufgerufene Funktion _public Zurück.
2. Übergeben Sie in der aufgerufenen Funktion _public Die Referenz this (um auf alle privaten Methoden und Requisiten zuzugreifen) , und alle Argumente von constructor (die in new Names() übergeben werden)
3. Im Funktionsumfang _public Gibt es auch die Klasse Names mit dem Zugriff auf die Referenz this (_this) der Klasse private Names

class Names {
  constructor() {
    this.privateProperty = 'John';
    return _public(this, arguments);
  }
  privateMethod() { }
}

const names = new Names(1,2,3);
console.log(names.somePublicMethod); //[Function]
console.log(names.publicProperty); //'Jasmine'
console.log(names.privateMethod); //undefined
console.log(names.privateProperty); //undefind

function _public(_this, _arguments) {
  class Names {
    constructor() {
      this.publicProperty = 'Jasmine';
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

    somePublicMethod() {
      _this.privateProperty; //"John";
      _this.privateMethod; //[Function]
    }

  }
  return new Names(..._arguments);
}
2
Paweł

Es ist möglich, private Methoden in Klassen zu verwenden, die WeakMap verwenden.

Gemäß MDN-Webdokumenten :

Das WeakMap-Objekt ist eine Sammlung von Schlüssel/Wert-Paaren, bei denen die Schlüssel nur Objekte sind und die Werte beliebige Werte sein können.

Die Objektreferenzen in den Schlüsseln werden schwach gehalten, was bedeutet, dass sie ein Ziel der Garbage Collection (GC) sind, wenn es keine anderen Referenzen mehr auf das Objekt gibt. 

Und dies ist ein Beispiel für das Erstellen einer Queue-Datenstruktur mit einem privaten Member _items, das ein Array enthält.

const _items = new WeakMap();

class Queue {    
    constructor() {
        _items.set(this, []);
    }

    enqueue( item) {
        _items.get(this).Push(item);
    }    

    get count() {
        return _items.get(this).length;        
    }

    peek() {
        const anArray = _items.get(this);
        if( anArray.length == 0)
            throw new Error('There are no items in array!');

        if( anArray.length > 0)
            return anArray[0];
    }

    dequeue() {        
        const anArray = _items.get(this);
        if( anArray.length == 0)
            throw new Error('There are no items in array!');

        if( anArray.length > 0)
            return anArray.splice(0, 1)[0];
    }    
}

Ein Beispiel für die Verwendung von:

const c = new Queue();
c.enqueue("one");
c.enqueue("two");
c.enqueue("three");
c.enqueue("four");
c.enqueue("five");
console.log(c);

Der private Member _items wird ausgeblendet und kann nicht in den Eigenschaften oder Methoden eines Queue-Objekts angezeigt werden:

 enter image description here

Das private Mitglied _items im Objekt Queue kann jedoch folgendermaßen aufgerufen werden:

const anArray = _items.get(this);
1
StepUp

Ein anderer Weg ähnlich den letzten beiden gepostet 

class Example {
  constructor(foo) {

    // privates
    const self = this;
    this.foo = foo;

    // public interface
    return self.public;
  }

  public = {
    // empty data
    nodata: { data: [] },
    // noop
    noop: () => {},
  }

  // everything else private
  bar = 10
}

const test = new Example('FOO');
console.log(test.foo); // undefined
console.log(test.noop); // { data: [] }
console.log(test.bar); // undefined
1
Jayesbe

Oh, so viele exotische Lösungen! Normalerweise kümmere ich mich nicht um die Privatsphäre, daher verwende ich "Pseudo Privacy", wie es hier gesagt ist. Wenn Sie sich jedoch darum kümmern (wenn es spezielle Anforderungen dafür gibt), verwende ich etwas wie in diesem Beispiel:

class jobImpl{
  // public
  constructor(name){
    this.name = name;
  }
  // public
  do(time){
    console.log(`${this.name} started at ${time}`);
    this.prepare();
    this.execute();
  }
  //public
  stop(time){
    this.finish();
    console.log(`${this.name} finished at ${time}`);
  }
  // private
  prepare(){ console.log('prepare..'); }
  // private
  execute(){ console.log('execute..'); }
  // private
  finish(){ console.log('finish..'); }
}

function Job(name){
  var impl = new jobImpl(name);
  return {
    do: time => impl.do(time),
    stop: time => impl.stop(time)
  };
}

// Test:
// create class "Job"
var j = new Job("Digging a ditch");
// call public members..
j.do("08:00am");
j.stop("06:00pm");

// try to call private members or fields..
console.log(j.name); // undefined
j.execute(); // error

Eine weitere mögliche Implementierung der Funktion (Konstruktor) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}
1
Sergey

Hier ist die Variable myThing privat und ist Teil der Schließung:

class Person {
  constructor() {

    var myThing = "Hello World";

    return {
      thing: myThing,
      sayThing: this.sayThing
    }
  }

  sayThing() {
    console.log(this.thing);
  }
}

var person = new Person();

console.log(person);

Sie können dieses https://www.npmjs.com/package/private-members ausprobieren.

Dieses Paket speichert die Mitglieder nach Instanz.

const pvt = require('private-members');
const _ = pvt();

let Exemplo = (function () {    
    function Exemplo() {
        _(this).msg = "Minha Mensagem";
    }

    _().mensagem = function() {
        return _(this).msg;
    }

    Exemplo.prototype.showMsg = function () {
        let msg = _(this).mensagem();
        console.log(msg);
    };

    return Exemplo;
})();

module.exports = Exemplo;
1
João Henrique

Die meisten Antworten geben entweder an, dass es unmöglich ist, oder erfordern die Verwendung einer WeakMap oder eines Symbols. Hierbei handelt es sich um ES6-Funktionen, für die wahrscheinlich Polyfills erforderlich sind. Es gibt jedoch einen anderen Weg! Schau dir 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 Accessormuster. Die grundlegende Idee ist, dass wir innerhalb der Schließung ein Schließung, einen Schlüssel haben und ein privates Objekt (im Konstruktor) erstellen, 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 nachlesen. Mit dieser Methode können Sie pro Objekteigenschaften erstellen, auf die außerhalb der Schließung nicht zugegriffen werden kann. Daher können Sie sie im Konstruktor oder Prototyp verwenden, aber nicht anderswo. Ich habe diese Methode nirgendwo verwendet, aber ich denke, sie ist wirklich mächtig.

1
guitarino

Wie wir wissen, gibt es keine native Unterstützung für private Eigenschaften mit ES6-Klassen.

Unten ist genau das, was ich benutze (kann hilfreich sein). Im Grunde packe ich eine Klasse in der Fabrik.

function Animal(name) {
    const privateData = 'NO experiments on animals have been done!';

    class Animal {
        constructor(_name) {
            this.name = _name;
        }
        getName() {
            return this.name
        }
        getDisclamer() {
            return `${privateData} Including ${this.name}`
        }
    }
    return new Animal(name)
}

Ich bin ein Anfänger und freue mich zu hören, ob dies ein schlechter Ansatz ist.

0

Beim Lesen der vorherigen Antwort dachte ich, dass dieses Beispiel die obigen Lösungen zusammenfassen kann

const friend = Symbol('friend');

const ClassName = ((hidden, hiddenShared = 0) => {

    class ClassName {
        constructor(hiddenPropertyValue, prop){
            this[hidden] = hiddenPropertyValue * ++hiddenShared;
            this.prop = prop
        }

        get hidden(){
            console.log('getting hidden');
            return this[hidden];
        }

        set [friend](v){
            console.log('setting hiddenShared');
            hiddenShared = v;
        }

        get counter(){
            console.log('getting hiddenShared');
            return hiddenShared;
        }

        get privileged(){
            console.log('calling privileged method');
            return privileged.bind(this);
        }
    }

    function privileged(value){
        return this[hidden] + value;
    }

    return ClassName;
})(Symbol('hidden'), 0);

const OtherClass = (() => class OtherClass extends ClassName {
    constructor(v){
        super(v, 100);
        this[friend] = this.counter - 1;
    }
})();
0
asdru

Ich habe ein Modul entwickelt, mit dem Sie die Zugriffsbeschränkung in der JavaScript-Klasse mit dem Namen Capsulable verwenden können. (Private & Protected Static)

Wenn Sie interessiert sind, schauen Sie sich mein Paket unten an . https://github.com/hmmhmmhm/capsulable

const Capsulable = require('capsulable')
const Field = Capsulable()

class A {
    constructor(_field){
        // Configure data fields.
        Field(this, _field)

        // The code below provides access to
        // the data fields when creating
        // functions within the class.
        Field(this).private
        Field(this).protected
        Field(this).protectedStatic
    }
}

module.exports = A
0
hmmhmmhm