Właściwości prywatne w klasach JavaScript ES6

444

Czy można tworzyć prywatne właściwości w klasach ES6?

Oto przykład. Jak mogę uniemożliwić dostęp instance.property?

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

var instance = new Something();
console.log(instance.property); //=> "test"
d13
źródło
5
Jest rzeczywiście etap 3 propozycja dla tej funkcji - tc39.github.io/proposal-class-fields github.com/tc39/proposal-class-fields
arty
@arty Odpowiedziałem na to z przykładami: stackoverflow.com/a/52237988/1432509
Alister

Odpowiedzi:

165

Prywatne pola (i metody) są wdrażane w standardzie ECMA . Możesz zacząć korzystać z nich już dziś z ustawieniem Babel 7 i etapu 3.

class Something {
  #property;

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

  #privateMethod() {
    return 'hello world';
  }

  getPrivateMessage() {
      return this.#privateMethod();
  }
}

const instance = new Something();
console.log(instance.property); //=> undefined
console.log(instance.privateMethod); //=> undefined
console.log(instance.getPrivateMessage()); //=> hello world
Alister
źródło
Zastanawiam się, jak mogą działać te pola klasowe. Obecnie nie można używać thisw konstruktorze przed wywołaniem super(). Jednak babel stawia je przed super.
seeker_of_bacon
Jak skonfigurować ESLint, aby zezwalał na #privateCrapskładnię?
Marecky,
6
A co z eslintem? Wystąpił błąd analizatora składni przy znaku równości. Babel działa, tylko eslint nie może przeanalizować tej nowej składni js.
martonx
6
Wow, to jest bardzo brzydkie. Hashtag jest prawidłową postacią. Nieruchomość nie jest tak naprawdę prywatna, czy? .. Sprawdziłem to w TypeScript. Członkowie prywatni nie są kompilowani w trybie prywatnym ani tylko do odczytu (z zewnątrz). Właśnie zadeklarowana jako inna (publiczna) własność. (ES5).
Dominik
2
Jak piszesz w ten sposób metody prywatne ? Czy mogę to zrobić #beep() {}:; i to async #bzzzt() {}:?
Константин Ван
277

Krótka odpowiedź, nie, nie ma natywnego wsparcia dla własności prywatnych z klasami ES6.

Ale możesz naśladować to zachowanie, nie dołączając nowych właściwości do obiektu, ale trzymając je w konstruktorze klas, i używaj metod pobierających i ustawiających, aby uzyskać dostęp do ukrytych właściwości. Zauważ, że metody pobierające i ustawiające są redefiniowane przy każdej nowej instancji klasy.

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; }
}
MetalGodwin
źródło
1
Najbardziej podoba mi się to rozwiązanie. Zgadzam się, że nie należy go używać do skalowania, ale jest idealny dla klas, które zwykle są tworzone tylko raz dla każdego dołączenia.
Blake Regalia,
2
Również redefiniujesz każdy element tej klasy za każdym razem, gdy tworzony jest nowy element.
Quentin Roy
10
To takie dziwne! W ES6 tworzysz więcej „piramid zamknięcia” niż przed ES6! Definiowanie funkcji W konstruktorze wygląda brzydiej niż w powyższym przykładzie ES5.
Kokodoko
1
Ponieważ OP konkretnie pyta o klasy ES6, osobiście uważam, że jest to złe rozwiązanie, mimo że technicznie działa. Głównym ograniczeniem jest to, że teraz każda metoda klasy, która wykorzystuje zmienne prywatne, musi zostać zadeklarowana w konstruktorze, co poważnie podważa zalety posiadania classskładni w pierwszej kolejności.
NanoWizard
10
Wszystko to wprowadza pośrednictwo. Jak teraz uczynić getNamei setNamewłaściwości prywatnymi?
aij
195

Aby rozwinąć odpowiedź @ loganfsmyth:

Jedynymi naprawdę prywatnymi danymi w JavaScript są wciąż zmienne o zasięgu. Nie można mieć właściwości prywatnych w sensie właściwości, do których dostęp jest uzyskiwany wewnętrznie w taki sam sposób, jak właściwości publicznych, ale można używać zmiennych o zasięgu do przechowywania danych prywatnych.

Zmienne o zasięgu

Podejście polega na wykorzystaniu zakresu funkcji konstruktora, która jest prywatna, do przechowywania prywatnych danych. Aby metody miały dostęp do tych prywatnych danych, muszą być również utworzone w konstruktorze, co oznacza, że ​​odtwarzasz je przy każdej instancji. Jest to kara za wydajność i pamięć, ale niektórzy uważają, że kara jest do zaakceptowania. Można uniknąć kary za metody, które nie potrzebują dostępu do prywatnych danych, dodając je jak zwykle do prototypu.

Przykład:

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

Scoped WeakMap

Można użyć WeakMap, aby uniknąć wydajności i kary pamięci z poprzedniego podejścia. WeakMaps kojarzy dane z Obiektami (tutaj instancje) w taki sposób, że można uzyskać do nich dostęp tylko przy użyciu tej WeakMap. Tak więc używamy metody zmiennych o zasięgu do utworzenia prywatnej WeakMap, a następnie używamy tej WeakMap do pobierania prywatnych danych związanych zthis . Jest to szybsze niż metoda zmiennych o zasięgu, ponieważ wszystkie instancje mogą współużytkować jedną WeakMap, więc nie trzeba ponownie tworzyć metod, aby umożliwić im dostęp do własnych WeakMap.

Przykład:

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

W tym przykładzie użyto obiektu do użycia jednej WeakMap dla wielu prywatnych właściwości; możesz także użyć wielu WeakMap i używać ich w podobny sposóbage.set(this, 20) , lub napisać małe opakowanie i użyć go w inny sposób, npprivateProps.set(this, 'age', 0) .

Prywatność tego podejścia mogłaby teoretycznie zostać naruszona przez manipulowanie globalnym WeakMap obiektem . To powiedziawszy, cały JavaScript może zostać uszkodzony przez zniekształcone globale. Nasz kod jest już oparty na założeniu, że tak się nie dzieje.

(Tę metodę można również wykonać Map, ale WeakMapjest lepsza, ponieważMap spowoduje przecieki pamięci, chyba że będziesz bardzo ostrożny, iw tym celu nie różnią się one inaczej.)

Połowa odpowiedzi: Symbole o zasięgu

Symbol jest rodzajem pierwotnej wartości, która może służyć jako nazwa właściwości. Możesz użyć metody zmiennej o zasięgu, aby utworzyć prywatny symbol, a następnie przechowywać prywatne dane pod adresemthis[mySymbol] .

Za pomocą tej zasady można naruszać prywatność tej metody Object.getOwnPropertySymbols , ale jest nieco niewygodna.

Przykład:

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)`.

Pół odpowiedzi: podkreślenia

Stare domyślne, wystarczy użyć właściwości publicznej z prefiksem podkreślenia. Chociaż konwencja nie jest w żaden sposób własnością prywatną, ta konwencja jest na tyle rozpowszechniona, że ​​wykonuje dobrą robotę, komunikując, że czytelnicy powinni traktować tę własność jako prywatną, co często kończy pracę. W zamian za ten upływ otrzymujemy podejście łatwiejsze do odczytania, łatwiejsze do pisania i szybsze.

Przykład:

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.

Wniosek

Począwszy od ES2017, nadal nie ma idealnego sposobu na robienie prywatnych nieruchomości. Różne podejścia mają zalety i wady. Zmienne o zasięgu są naprawdę prywatne; scakowane WeakMapy są bardzo prywatne i bardziej praktyczne niż zmienne zakresowe; Symbole o zasięgu są w miarę prywatne i praktyczne; podkreślenia są często dość prywatne i bardzo praktyczne.

Tristan
źródło
7
Pierwszy przykładowy fragment kodu („zmienne o zasięgu”) jest całkowitym anty-wzorem - każdy zwracany obiekt będzie miał inną klasę. Nie rób tego Jeśli chcesz uprzywilejowanych metod, utwórz je w konstruktorze.
Bergi,
1
Owinięcie klasy wewnątrz funkcji wydaje się w pierwszej kolejności zniweczyć cały cel używania klas. Jeśli już używasz tej funkcji do utworzenia instancji, równie dobrze możesz umieścić wszystkich członków prywatnych / publicznych w tej funkcji i zapomnieć o całym słowie kluczowym klasy.
Kokodoko
2
@Bergi @Kokodoko Zredagowałem podejście do zmiennych o zmiennym zasięgu, aby były nieco szybsze i nie ulegały uszkodzeniu instanceof. Przyznaję, że myślałem o tym podejściu uwzględnionym wyłącznie dla kompletności i powinienem był więcej zastanowić się nad tym, na ile faktycznie jest w stanie.
tristan
1
Doskonałe wyjaśnienie! Nadal jestem zaskoczony, że ES6 utrudnił symulację zmiennej prywatnej, gdzie w ES5 można po prostu użyć var, a to wewnątrz funkcji do symulacji prywatnych i publicznych.
Kokodoko
2
@Kokodoko Jeśli zrezygnujesz z klasy i po prostu umieścisz wszystko w funkcji, będziesz również musiał wrócić do implementacji dziedziczenia za pomocą metody prototypowej. Korzystanie z rozszerzania na klasach jest zdecydowanie czystszym podejściem, więc użycie klasy wewnątrz funkcji jest całkowicie dopuszczalne.
AndroidDev
117

Aktualizacja: przygotowywana jest propozycja z ładniejszą składnią . Wkłady są mile widziane.


Tak, istnieje - dla dostępu do obiektów w zasięguSymbol - ES6 wprowadza s .

Symbole są unikalne, nie możesz uzyskać dostępu do jednego z zewnątrz, z wyjątkiem odbicia (jak prywatne w Javie / C #), ale każdy, kto ma dostęp do symbolu wewnątrz, może go użyć do uzyskania klucza:

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
Benjamin Gruenbaum
źródło
6
Nie można używać Object.getOwnPropertySymbols? ;)
Qantas 94 Heavy
41
@BenjaminGruenbaum: Najwyraźniej symbole nie zapewniają już prawdziwej prywatności: stackoverflow.com/a/22280202/1282216
d13
28
@trusktr przez trzy klucze? Nie. Poprzez symbole? Tak. Bardzo podobnie jak możesz używać odbicia w językach takich jak C # i Java, aby uzyskać dostęp do pól prywatnych. Modyfikatory dostępu nie polegają na bezpieczeństwie - chodzi o jasność intencji.
Benjamin Gruenbaum
9
Wygląda na to, że używanie symboli jest podobne do działania const myPrivateMethod = Math.random(); Something.prototype[''+myPrivateMethod] = function () { ... } new Something()[''+myPrivateMethod]();. To nie jest tak naprawdę prywatność, to niejasność w sensie tradycyjnego JavaScript. Uważam, że „prywatny” JavaScript oznacza używanie zamknięć do enkapsulacji zmiennych. Te zmienne nie są zatem dostępne poprzez odbicie.
trusktr
13
Ponadto uważam, że użycie słów kluczowych privatei protectedbyłoby o wiele czystsze niż Symbollub Name. Wolę notację kropkową niż notację nawiasową. Chciałbym nadal używać kropki do prywatnych rzeczy. this.privateVar
trusktr
33

Odpowiedź brzmi nie". Ale możesz utworzyć prywatny dostęp do takich nieruchomości:

(Sugestia, że ​​Symbole mogą być używane do zapewnienia prywatności, była prawdziwa we wcześniejszej wersji specyfikacji ES6, ale nie jest już tak: https://mail.mozilla.org/pipermail/es-discuss/2014- stycznia / 035604. HTML i https://stackoverflow.com/a/22280202/1282216 . Aby uzyskać dłuższą dyskusję na temat symboli i prywatności, zobacz: https://curiosity-driven.org/private-properties-in-javascript )

d13
źródło
6
-1, to naprawdę nie odpowiada na twoje pytanie. (Możesz również używać zamknięć z IIFE w ES5). Własności prywatne można wyliczyć poprzez odbicie w większości języków (Java, C # itp.). Istotą własności prywatnej jest przekazywanie zamiarów innym programistom, a nie egzekwowanie bezpieczeństwa.
Benjamin Gruenbaum,
1
@BenjaminGruenbaum, wiem, chciałbym mieć lepszą odpowiedź, też nie jestem z tego zadowolony.
d13
Myślę, że symbole są wciąż ważnym sposobem na uzyskanie niedostępnych elementów w środowisku programowania. Tak, nadal można je znaleźć, jeśli naprawdę chcesz, ale nie o to chodzi? Nie powinieneś przechowywać w nim poufnych informacji, ale i tak nie powinieneś tego robić w kodzie klienta. Ale działa w celu ukrycia właściwości lub metody przed klasą zewnętrzną.
Kokodoko
Używanie zmiennych o zasięgu na poziomie modułu jako substytutu własności prywatnych w klasie doprowadzi do zachowania singleton. Lub zachowania podobnego do właściwości statitc. Substancje zmiennych zostaną udostępnione.
Adrian Moisa
30

Jedynym sposobem na uzyskanie prawdziwej prywatności w JS jest określenie zakresu, więc nie ma możliwości, aby właściwość, która jest jej członkiem this, była dostępna tylko wewnątrz komponentu. Najlepszym sposobem przechowywania prawdziwie prywatnych danych w ES6 jest 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));
  }
}

Oczywiście jest to prawdopodobnie powolne i zdecydowanie brzydkie, ale zapewnia prywatność.

Pamiętaj, że NAWET TO NIE jest idealne, ponieważ JavaScript jest tak dynamiczny. Ktoś może jeszcze to zrobić

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

aby przechwytywać wartości podczas ich przechowywania, więc jeśli chcesz zachować szczególną ostrożność, musisz przechwycić lokalne odwołanie do .seti .getużyć jawnie zamiast polegać na nadpisywalnym prototypie.

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));
  }
}
loganfsmyth
źródło
3
Jako sugestię można uniknąć użycia jednej słabej mapy na właściwość, używając obiektu jako wartości. W ten sposób możesz również zmniejszyć liczbę map getdo jednej na metodę (np const _ = privates.get(this); console.log(_.privateProp1);.).
Quentin Roy
Tak, to też jest całkowicie opcja. Przeważnie poszedłem z tym, ponieważ mapuje ono bardziej bezpośrednio to, co użytkownik napisałby, używając prawdziwych nieruchomości.
loganfsmyth
@loganfsmyth const myObj = new SomeClass(); console.log(privateProp1.get(myObj)) // "I am Private1"oznacza, że ​​twoja własność jest prywatna czy nie?
Barbu Barbu
2
Aby to zadziałało, kod uzyskujący dostęp do właściwości wymagałby dostępu do obiektu WeakMap, który normalnie miałby zasięg w module i
byłby
22

W celu odniesienia się do innych osób poszukujących w przyszłości, słyszę teraz, że zaleceniem jest użycie WeakMaps do przechowywania prywatnych danych.

Oto wyraźniejszy, działający przykład:

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}`)
      }
    }
  }
}
Społeczność
źródło
20
Pamiętaj, że te właściwości są statyczne.
Michael Theriot
8
Nie głosowałem za tobą, ale twój przykład słabej mapy jest całkowicie błędny.
Benjamin Gruenbaum
4
Mianowicie - dzielisz dane między wszystkimi instancjami klasy, a nie na instancję - czy mogę to przynajmniej naprawić?
Benjamin Gruenbaum
1
Rzeczywiście, słaba mapa musi być dołączona do danej instancji. Zobacz fitzgeraldnick.com/weblog/53 dla przykładu.
poszerzył się
2
Według MDN prymitywne typy danych, takie jak Symbole, nie są dozwolone jako klucz WeakMap. Dokumentacja MDN WeakMap
leepowell
12

Zależy od kogo pytasz :-)

Żaden privatemodyfikator właściwości nie jest zawarty w propozycji maksymalnie minimalnych klas, która wydaje się, że znalazła się w bieżącej wersji roboczej .

Może jednak istnieć wsparcie dla prywatnych nazw , które dopuszczają prywatne własności - i prawdopodobnie mogłyby być również użyte w definicjach klas.

Bergi
źródło
3
Jest bardzo mało prawdopodobne, że prywatne nazwy pojawią się w ES6, choć myślą o jakiejś formie prywatnej dla ES7.
Qantas 94 Heavy
@ Qantas94Heavy zarówno prywatne nazwy, jak i unikalne wartości ciągów zostały zastąpione przez Symbole z tego, co rozumiem.
Benjamin Gruenbaum,
Tak, prawdopodobnie staną się Symbolami. Jednak afaik „symbole” obecnie zawarte w specyfikacji są używane tylko do opisywania wewnętrznych właściwości, takich jak [[prototyp]], i nie ma możliwości ich tworzenia i używania w kodzie użytkownika. Czy znasz jakieś dokumenty?
Bergi
Właśnie zdałem sobie sprawę, że z modułów można ustawić prywatność. W połączeniu z symbolami, które mogą być wszystkim, czego kiedykolwiek potrzebujesz ...?
d13
1
@Cody: Cały kod modułu i tak ma swój własny zakres w ES6, bez potrzeby IEFE. I tak, symbole mają na celu unikalność (unikanie kolizji), a nie prywatność.
Bergi,
10

Korzystanie z modułów ES6 (początkowo proponowanych przez @ d13) działa dobrze dla mnie. Nie naśladuje on doskonale własności prywatnych, ale przynajmniej możesz mieć pewność, że właściwości, które powinny być prywatne, nie wyciekną poza twoją klasę. Oto przykład:

coś.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');
  }
};

W takim przypadku konsumujący kod może wyglądać następująco:

import Something from './something.js';

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

Aktualizacja (ważne):

Jak wspomniano w komentarzach @DanyalAytekin, te własności prywatne są statyczne, dlatego mają zasięg globalny. Będą dobrze działać podczas pracy z Singletonami, ale należy zachować ostrożność w przypadku obiektów przejściowych. Rozszerzając powyższy przykład:

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
Johnny Oshika
źródło
4
Dobre dla private static.
Danyal Aytekin
@DanyalAytekin: to bardzo dobra uwaga. Te własności prywatne są statyczne, więc mają zasięg globalny. Zaktualizowałem swoją odpowiedź, aby to odzwierciedlić.
Johnny Oshika
Im więcej dowiaduję się o programowaniu funkcjonalnym (szczególnie Elm i Haskell), tym bardziej wierzę, że programiści JS skorzystaliby na modułowym podejściu do „modułowości”, a nie opartym na klasie OOP. Jeśli pomyślimy o modułach ES6 jako fundamentach do budowania aplikacji i całkowicie zapomnimy o klasach, uważam, że ogólnie możemy uzyskać znacznie lepsze aplikacje. Czy doświadczeni użytkownicy Elm lub Haskell mogliby skomentować to podejście?
d13
1
W aktualizacji drugim a.say(); // apowinno byćb.say(); // b
grokky
wypróbowany let _message = nullsposób, nie tak fajny, gdy wywołuje konstruktora wiele razy, to psuje.
Littlee
9

Wypełnianie @ d13 i komentarze @ johnny-oshika i @DanyalAytekin:

Myślę, że w przykładzie podanym przez @ johnny-oshika moglibyśmy użyć normalnych funkcji zamiast funkcji strzałek, a następnie .bindz bieżącym obiektem plus_privates obiektem jako parametrem curry:

coś.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

Korzyści, które mogę wymyślić:

  • możemy mieć metody prywatne ( _greeti _updateMessagezachowywać się jak metody prywatne, dopóki tego nie robimy)export referencji)
  • chociaż nie znajdują się w prototypie, wyżej wymienione metody oszczędzają pamięć, ponieważ instancje są tworzone raz, poza klasą (w przeciwieństwie do definiowania ich w konstruktorze)
  • nie wyciekamy żadnych globałów, ponieważ jesteśmy w module
  • możemy również mieć prywatne właściwości za pomocą powiązanego _privatesobiektu

Niektóre wady, które mogę wymyślić:

Działający fragment kodu można znaleźć tutaj: http://www.webpackbin.com/NJgI5J8lZ

efidile
źródło
8

Tak - możesz utworzyć właściwość kapsułkowaną , ale nie zrobiono tego za pomocą modyfikatorów dostępu (public | private), przynajmniej nie w ES6.

Oto prosty przykład, jak można to zrobić za pomocą ES6:

1 Utwórz klasę za pomocą klasy słowa

2 wewnątrz jest konstruktor stwierdzenie bloku scoped zmienna użyciu let LUB const zasięgu blokowym, słów „ -> ponieważ mają one zasięg blokowy, nie można uzyskać do nich dostępu z zewnątrz (enkapsulowane)

3 Aby umożliwić kontrolę dostępu (setters | getters) do tych zmiennych, możesz zadeklarować metodę instancji wewnątrz jej konstruktora, używając: this.methodName=function(){}składni

"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;
            }
        }
    }

Teraz sprawdźmy to:

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
Nikita Kurtin
źródło
1
Jest to (na razie) jedyne rozwiązanie tego problemu, pomimo faktu, że wszystkie metody zadeklarowane w konstruktorze są ponownie zadeklarowane dla każdej instancji klasy. To dość zły pomysł na wydajność i wykorzystanie pamięci. Metody klas powinny być deklarowane poza zakresem konstruktora.
Freezystem
@Freezystem Po pierwsze: po pierwsze są to metody instancji (nie metody klasy). Drugie pytanie PO brzmiało: _ Jak mogę zapobiec dostępowi do instance.property?_, a moja odpowiedź brzmi: przykład tego, jak ... Po trzecie, jeśli masz lepszy pomysł - usłyszmy to
Nikita Kurtin
1
Nie mówiłem, że się myliłeś, powiedziałem, że twoje rozwiązanie jest najlepszym kompromisem do uzyskania zmiennej prywatnej, pomimo faktu, że kopia każdej metody instancji jest tworzona za każdym razem, gdy wywołujesz, new Something();ponieważ twoje metody są deklarowane w konstruktorze, aby mieć do nich dostęp zmienne prywatne. Może to powodować duże zużycie pamięci, jeśli utworzysz wiele wystąpień swojej klasy, więc problemy z wydajnością. Metody powinny zostać zadeklarowane poza zakresem konstruktora. Mój komentarz był raczej wyjaśnieniem wad twojego rozwiązania niż krytyką.
Freezystem
1
Ale czy to nie zła praktyka definiuje całą klasę wewnątrz konstruktora? Czy teraz nie „hakujemy” javascript? Wystarczy spojrzeć na inny język programowania OOP, a zobaczysz, że konstruktor nie ma na celu definiowania klasy.
Kokodoko
1
Tak, o to mi chodziło, a twoje rozwiązanie działa! Po prostu mówię, że ogólnie jestem zaskoczony, że ES6 dodał słowo kluczowe „klasa”, ale usunąłem eleganckie rozwiązanie pracy z var i tym, aby uzyskać enkapsulację.
Kokodoko,
8

Inne podejście do „prywatnego”

Zamiast walczyć z tym, że prywatna widoczność jest obecnie niedostępna w ES6, postanowiłem zastosować bardziej praktyczne podejście, które jest w porządku, jeśli twoje IDE obsługuje JSDoc (np. Webstorm). Chodzi o to, aby użyć @privatetagu . Jeśli chodzi o rozwój, IDE uniemożliwi ci dostęp do dowolnego członka prywatnego spoza jego klasy. Działa całkiem dobrze dla mnie i było bardzo przydatne do ukrywania wewnętrznych metod, więc funkcja autouzupełniania pokazuje mi, co klasa naprawdę chciała ujawnić. Oto przykład:

autouzupełnianie pokazujące tylko rzeczy publiczne

Lucio Paiva
źródło
1
Problem polega na tym, że nie chcemy uzyskać dostępu do zmiennych prywatnych w edytorze, nie chcemy chronić zmiennych prywatnych z zewnątrz - i to właśnie robi public / private. Jeśli twój kod jest gotowy, możesz uzyskać dostęp (i ważne, aby: zastąpić ) te zmienne spoza klasy. Twój @privatekomentarz nie może temu zapobiec, jest to tylko funkcja do generowania dokumentacji i masz IDE.
Adrian Preuss
Tak, jestem tego świadomy. Po prostu to mi wystarcza i może być wystarczające dla innych ludzi. Wiem, że tak naprawdę nie czyni moich zmiennych prywatnymi; ostrzega mnie tylko, aby nie próbować uzyskiwać do niego dostępu z zewnątrz (oczywiście tylko wtedy, gdy mój zespół i ja używamy IDE obsługującego tę funkcję). Javascript (i inne języki, takie jak Python) nie zostały zaprojektowane z myślą o poziomach dostępu. Ludzie robią różne rzeczy, aby jakoś zaimplementować tę funkcjonalność, ale ostatecznie hakujemy język, aby to osiągnąć. Zdecydowałem się na bardziej „naturalne” podejście, jeśli chcesz.
Lucio Paiva,
6

WeakMap

  • obsługiwane w IE11 (symbole nie są)
  • hard-private (rekwizyty używające symboli są soft-private z powodu Object.getOwnPropertySymbols)
  • może wyglądać naprawdę czysto (w przeciwieństwie do zamknięć, które wymagają wszystkich rekwizytów i metod w konstruktorze)

Najpierw zdefiniuj funkcję do zawijania WeakMap:

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

Następnie zbuduj referencję poza klasą:

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
  }
}

Uwaga: klasa nie jest obsługiwana przez IE11, ale wygląda lepiej w tym przykładzie.

kevlened
źródło
6

Och, tyle egzotycznych rozwiązań! Zwykle nie dbam o prywatność, więc używam „pseudo-prywatności”, jak tu powiedziano . Ale jeśli to ważne (jeśli istnieją specjalne wymagania) używam czegoś takiego jak w tym przykładzie:

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

Inna możliwa implementacja funkcji (konstruktora) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}
Siergiej
źródło
5

Osobiście podoba mi się propozycja operatora bind, :: a następnie połączę ją ze wspomnianym rozwiązaniem @ d13, ale na razie trzymaj się odpowiedzi @ d13, gdzie używasz exportsłowa kluczowego dla swojej klasy i umieszczasz prywatne funkcje w module.

jest jeszcze jedno trudne rozwiązanie, o którym nie wspomniano tutaj, które jest bardziej funkcjonalne i pozwoliłoby na posiadanie wszystkich prywatnych rekwizytów / metod w klasie.

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

komentarze na ten temat będą mile widziane.

Robin F.
źródło
Ogólnie podoba mi się to podejście. Informacja zwrotna: 1. będziesz potrzebował innego modułu private.js dla każdej klasy, aby zapobiec kolizji. 2. Nie podoba mi się potencjał tworzenia naprawdę długiego konstruktora poprzez bezpośrednie definiowanie każdej z twoich prywatnych metod. 3. Byłoby miło, gdyby wszystkie metody klas były w jednym pliku.
Doug Coburn,
5

Natknąłem się na ten post, gdy szukałem najlepszych praktyk w zakresie „prywatnych danych dla klas”. Wspomniano, że niektóre wzorce będą miały problemy z wydajnością.

Złożyłem kilka testów jsperf opartych na 4 głównych wzorach z książki online „Exploring ES6”:

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

Testy można znaleźć tutaj:

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

W Chrome 63.0.3239 / Mac OS X 10.11.6 najskuteczniejszymi wzorcami były „Prywatne dane za pośrednictwem środowisk konstruktorów” i „Prywatne dane za pośrednictwem konwencji nazewnictwa”. Dla mnie Safari wypadło dobrze dla WeakMap, ale Chrome nie tak dobrze.

Nie znam wpływu pamięci, ale wzorzec „środowisk konstruktorów”, który, jak ostrzegali niektórzy, byłby problem z wydajnością, był bardzo wydajny.

4 podstawowe wzory to:

Prywatne dane za pośrednictwem środowisk konstruktorów

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();

Prywatne dane za pośrednictwem środowisk konstruktorów 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();

Prywatne dane za pomocą konwencji nazewnictwa

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();

Prywatne dane za pośrednictwem 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();

Prywatne dane za pomocą symboli

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();
MarkM
źródło
4

Wierzę, że możliwe jest uzyskanie „najlepszego z obu światów” za pomocą zamknięć wewnątrz konstruktorów. Istnieją dwie odmiany:

Wszyscy członkowie danych są prywatni

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);
   }
};

Niektórzy członkowie są prywatni

UWAGA: Jest to wprawdzie brzydkie. Jeśli znasz lepsze rozwiązanie, edytuj tę odpowiedź.

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);
   }
};

JSInitiate
źródło
4

W rzeczywistości jest to możliwe przy użyciu symboli i serwerów proxy. Używasz symboli w zakresie klasy i ustawiasz dwie pułapki w proxy: jeden dla prototypu klasy, tak aby Reflect.ownKeys (instancja) lub Object.getOwnPropertySymbols nie oddawał twoich symboli, drugi jest dla samego konstruktora więc kiedy new ClassName(attrs)zostanie wywołane, zwrócona instancja zostanie przechwycona i będzie miała zablokowane symbole własnych właściwości. Oto kod:

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()działa tak: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj))dlatego potrzebujemy pułapki na te obiekty.

Francisco Neto
źródło
Dzięki, wypróbuję symbole :) Ze wszystkich powyższych odpowiedzi wydaje się najprostszym sposobem na utworzenie niedostępnego członka klasy :)
Kokodoko
4

Nawet maszynopis nie może tego zrobić. Z ich dokumentacji :

Gdy członek jest oznaczony jako prywatny, nie można uzyskać do niego dostępu spoza jego zawierającej klasy. Na przykład:

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

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

Ale przeniesione na ich plac zabaw daje to:

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

Dlatego ich „prywatne” słowo kluczowe jest nieskuteczne.

Michael Franzl
źródło
2
Cóż, nadal jest skuteczny, ponieważ zapobiega „złemu” programowaniu w IDE. Pokazuje, których członków powinieneś, a których nie powinieneś używać. Myślę, że to główny powód używania prywatnego i publicznego. (Na przykład, kiedy kompilujesz C # do kodu maszynowego, czy prywatny nadal będzie prywatny? Kto wie?). Podczas czytania innych odpowiedzi wydaje się, że użycie @Symbol może również uczynić członka niedostępnym. Ale nawet symbole nadal można znaleźć w konsoli.
Kokodoko
Czy błąd TypeScript występuje podczas transponowania TypeScript na JavaScript? (Podobnie jak sprawdzanie typu odbywa się w czasie transpozycji. Zamiast prywatnego mechanizmu wykonawczego.)
Eljay
4

Spóźniam się na tę imprezę, ale trafiłem na pytanie OP w poszukiwaniu, więc ... Tak, możesz mieć prywatne nieruchomości, zawijając deklarację klasy w zamknięciu

Jest przykład tego, jak mam prywatne metody w tym codepen . W poniższym fragmencie klasa Subscribable ma dwie funkcje „prywatne” processi processCallbacks. Wszelkie właściwości można dodawać w ten sposób i są one poufne dzięki zastosowaniu zamknięcia. Prywatność IMO jest rzadką potrzebą, jeśli obawy są dobrze rozdzielone, a JavaScript nie musi być rozdęty przez dodanie większej składni, gdy zamknięcie starannie wykonuje zadanie.

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)}}}})();

Podoba mi się to podejście, ponieważ ładnie rozdziela obawy i zapewnia prywatność. Jedynym minusem jest konieczność użycia „ja” (lub czegoś podobnego) w celu odniesienia do „tego” w treści prywatnej.

Paul Whipp
źródło
4

Myślę, że odpowiedź Benjamina jest prawdopodobnie najlepsza w większości przypadków, dopóki język natywnie nie obsługuje jawnie prywatnych zmiennych.

Jeśli jednak z jakiegoś powodu musisz uniemożliwić dostęp Object.getOwnPropertySymbols(), metodą, którą rozważałem, jest dołączenie unikalnej, nieskonfigurowalnej, niepoliczalnej, niepisywalnej właściwości, której można użyć jako identyfikatora właściwości do każdego obiektu w budowie (np. unikatowy Symbol, jeśli nie masz jeszcze innej unikalnej właściwości, takiej jak an id). Następnie po prostu przechowuj mapę „prywatnych” zmiennych każdego obiektu za pomocą tego identyfikatora.

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"

Potencjalną zaletą tego podejścia w porównaniu z użyciem WeakMapjest szybszy czas dostępu, jeśli wydajność staje się problemem.

NanoWizard
źródło
1
Popraw mnie, jeśli się mylę, ale czy ten kod nie zawiera wycieków pamięci, ponieważ privateVars nadal będą przechowywać prywatne zmienne obiektu, nawet jeśli obiekt jest już zniszczony?
Russell Santos
@ RussellSantos masz rację, zakładając, że w pewnym momencie obiekty będą musiały zostać wyrzucone Dziękuję za zwrócenie na to uwagi. W moim przykładzie dodałem destroy()metodę, która powinna być wywoływana za pomocą kodu za każdym razem, gdy trzeba usunąć obiekt.
NanoWizard
4

Tak, całkowicie można i całkiem łatwo. Odbywa się to poprzez ujawnienie prywatnych zmiennych i funkcji poprzez zwrócenie prototypowego wykresu obiektów w konstruktorze. To nic nowego, ale weź trochę js foo, aby zrozumieć jego elegancję. W ten sposób nie używa się globalnego zasięgu ani słabych map. Jest to forma refleksji wbudowana w język. W zależności od tego, jak wykorzystasz to; można albo wymusić wyjątek, który przerywa stos wywołań, albo zakopać wyjątek jako undefined. Zostało to przedstawione poniżej i można przeczytać więcej o tych funkcjach tutaj

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)); //1
console.log(clazz.public2(2)); //2
console.log(clazz.public3(3)); //27
console.log(clazz.private(0)); //error

1-14x0r
źródło
3
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"
Ilya Zarembsky
źródło
2
Najlepiej unikać odpowiedzi tylko na kod. Byłoby lepiej, gdybyś mógł wyjaśnić, w jaki sposób twój kod odpowiada na pytanie OP
Stewart_R
Tak naprawdę można uczynić zmienną tylko do odczytu bardziej zmienną prywatną. Zmienna prywatna nie powinna być dostępna na zewnątrz. console.log(instance.property)powinien rzucić lub dać ci niezdefiniowany, a nie dać ci „testu”.
oooyaya
3

Inny sposób podobny do dwóch ostatnich opublikowanych

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
Jayesbe
źródło
2

Większość odpowiedzi albo mówi, że jest to niemożliwe, albo wymaga użycia WeakMap lub Symbol, które są funkcjami ES6, które prawdopodobnie wymagałyby wielokrotnych wypełnień. Jest jednak inny sposób! Sprawdź to:

// 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

Nazywam tę metodę wzorcem akcesorium . Podstawową ideą jest to, że mamy zamknięcie , klucz wewnątrz zamknięcia i tworzymy prywatny obiekt (w konstruktorze), do którego można uzyskać dostęp tylko, jeśli masz klucz .

Jeśli jesteś zainteresowany, możesz przeczytać więcej na ten temat w moim artykule . Za pomocą tej metody można tworzyć właściwości dla każdego obiektu, do których nie można uzyskać dostępu poza zamknięciem. Dlatego możesz ich używać w konstruktorze lub prototypie, ale nigdzie indziej. Nigdzie nie widziałem tej metody, ale myślę, że jest naprawdę potężna.

gitara
źródło
Pytanie dotyczyło tego, jak to osiągnąć w klasach ES6.
Michael Franzl,
Tej samej metody można użyć w klasach ES6. Klasy ES6 to głównie cukier oprócz funkcji, które przedstawiłem w moim przykładzie. Całkiem możliwe, że oryginalny plakat używa transpilatora, w którym to przypadku WeakMaps lub Symbole nadal będą wymagać wypełniania. Moja odpowiedź jest ważna niezależnie.
guitarino
2

Zobacz tę odpowiedź, aby uzyskać czyste i proste rozwiązanie „klasowe” z prywatnym i publicznym interfejsem oraz obsługą kompozycji

Kofifus
źródło
2

Znalazłem bardzo proste rozwiązanie, po prostu użyj Object.freeze(). Oczywiście problem polega na tym, że nie możesz później dodać niczego do obiektu.

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
Nikola Andreev
źródło
spowoduje to również wyłączenie metody ustawiającej, takiej jaksetName(name) { this.name = name; }
ngakak,
2

Używam tego wzoru i zawsze działa dla mnie

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

Yami Teru
źródło
2

Właściwie jest to możliwe.
1. Najpierw utwórz klasę i w konstruktorze zwróć wywoływaną _publicfunkcję.
2. W wywoływanej _publicfunkcji przekaż thisodwołanie (aby uzyskać dostęp do wszystkich prywatnych metod i rekwizytów) i wszystkich argumentów z constructor (które zostaną przekazane new Names())
3. W zakresie _publicfunkcji znajduje się również Namesklasa z dostępem do this(_this ) odniesienie do Namesklasy prywatnej

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);
}
Paweł
źródło
2

Możesz spróbować https://www.npmjs.com/package/private-members

Ten pakiet zapisze członków według instancji.

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;
João Henrique
źródło