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

Czy jest możliwe tworzenie prywatnych właściwości w klasach ES6?

Oto przykład. Jak mogę zapobiec dostępowi do instance.property?
class Something {
  constructor(){
    this.property = "test";
  }
}

var instance = new Something();
console.log(instance.property); //=> "test"
Author: Kirill Fuchs, 2014-03-03

30 answers

Prywatne pola (i metody) są implementowane w standardzie ECMA . Możesz zacząć ich używać już dziś z babel 7 i ustawieniem stage 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
 202
Author: Alister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-03 18:16:01

Krótka odpowiedź: Nie, Nie ma natywnego wsparcia dla właściwoś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 wewnątrz konstruktora klasy i używając getterów i setterów, aby dotrzeć do ukrytych właściwości. Zauważ, że gettery i settery są redefiniowane dla 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; }
}
 296
Author: MetalGodwin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-11 19:33:47

Aby rozwinąć odpowiedź @ loganfsmyth:

Jedynymi prawdziwie prywatnymi danymi w JavaScript są zmienne o zasięgu. Właściwości prywatne w sensie właściwości nie mogą być dostępne wewnętrznie w taki sam sposób jak właściwości publiczne, ale do przechowywania prywatnych danych można używać zmiennych zakresowych.

Zmienne zakresowe

Tutaj podejście polega na użyciu 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 z każdą instancją. Jest to kara występu i pamięci, ale niektórzy uważają, że kara jest do przyjęcia. Kary można uniknąć w przypadku metod, które nie potrzebują dostępu do prywatnych danych, dodając je do prototypu jak zwykle.

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

Słaba mapa może być używana, aby uniknąć kary za wydajność i pamięć poprzedniego podejścia. WeakMaps kojarzy dane z Obiekty (tutaj instancje) w taki sposób, że można do nich uzyskać dostęp tylko za pomocą tej Weakmapy. Tak więc, używamy metody scoped variables do tworzenia prywatnej Weakmapy, a następnie używamy tej Weakmapy do pobierania prywatnych danych powiązanych z this. Jest to szybsze niż metoda zmiennych zakresowych, ponieważ wszystkie instancje mogą współdzielić jedną Weakmapę, więc nie musisz odtwarzać metod tylko po to, aby uzyskać 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

Ten przykład używa obiektu do użycia jednego WeakMap dla wielu prywatnych właściwości; Możesz również użyć wielu WeakMap i użyć ich jak age.set(this, 20), lub napisać mały wrapper i użyć go w inny sposób, jak privateProps.set(this, 'age', 0).

Prywatność takiego podejścia może teoretycznie zostać naruszona przez manipulację globalnym obiektem WeakMap. To powiedziawszy, wszystkie JavaScript mogą być łamane przez zniekształcone globale. Nasz kod jest już zbudowany na założeniu, że tak się nie dzieje.

(ta metoda może być również wykonana z Map, Ale WeakMap jest lepsza, ponieważ Map spowoduje wycieki pamięci, chyba że będziesz bardzo ostrożny, i w tym celu nie różnią się one inaczej.)

Half-Answer: Scoped Symbols

Symbol jest rodzajem prymitywnej wartości, która może służyć jako nazwa właściwości. Możesz użyć metody zmiennej zakresowej do utworzenia symbolu prywatnego, a następnie zapisać prywatne dane w this[mySymbol].

Prywatność tej metody może być naruszona za pomocą Object.getOwnPropertySymbols, ale jest nieco niezręczna dla zrób.

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

Połowa Odpowiedzi: Podkreślenia

Stara wartość domyślna, wystarczy użyć właściwości publicznej z prefiksem podkreślenia. Choć w żaden sposób nie jest to własność prywatna, konwencja ta jest na tyle rozpowszechniona, że dobrze radzi sobie z komunikowaniem, że czytelnicy powinni traktować własność jako prywatną, co często wykonuje zadanie. W zamian za ten błąd otrzymujemy podejście, które jest łatwiejsze do odczytania, łatwiejsze do wpisania i szybciej.

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.

Podsumowanie

[13]} od ES2017, nadal nie ma doskonałego sposobu, aby zrobić prywatne nieruchomości. Różne podejścia mają plusy i minusy. Zmienne zakresowe są naprawdę prywatne; słabe Mapy zakresowe są bardzo prywatne i bardziej praktyczne niż zmienne zakresowe; Symbole zakresowe są w miarę prywatne i w miarę praktyczne; podkreślenia są często wystarczająco prywatne i bardzo praktyczne.
 207
Author: tristan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-27 23:08:19

Update: propozycja o ładniejszej składni jest już w drodze. Wkład jest mile widziany.


Tak, Istnieje - dla dostępu do obiektów - ES6 wprowadza Symbols .

Symbole są unikalne, nie można uzyskać dostępu do jednego z zewnątrz, z wyjątkiem odbicia (jak privates w Java/C#), ale każdy, kto ma dostęp do symbolu wewnątrz, może użyć go do dostępu do 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
 118
Author: Benjamin Gruenbaum,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-10-06 03:29:30

Odpowiedź brzmi "nie". Ale możesz utworzyć prywatny dostęp do właściwości takich jak:

(sugestia, że symbole mogą być używane w celu zapewnienia prywatności była prawdziwa we wcześniejszej wersji specyfikacji ES6, ale nie jest już case: https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html i https://stackoverflow.com/a/22280202/1282216 . dla dłuższej dyskusji na temat symboli i prywatności patrz: https://curiosity-driven.org/private-properties-in-javascript )

 34
Author: d13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:36

Jedynym sposobem na uzyskanie prawdziwej prywatności w JS jest scoping, więc nie ma sposobu na posiadanie właściwości, która jest członkiem this, która będzie dostępna tylko wewnątrz komponentu. Najlepszym sposobem na przechowywanie prawdziwie prywatnych danych w ES6 jest użycie Weakmapy.

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 powolny i zdecydowanie brzydki, ale zapewnia prywatność.

Pamiętaj, że nawet to nie jest idealne, ponieważ Javascript jest tak dynamiczny. Ktos moze jeszcze zrobic

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

Aby wyłapać wartości jako są one przechowywane, więc jeśli chcesz być bardzo ostrożny, musisz przechwycić lokalne odniesienie do .set i .get, aby uż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));
  }
}
 30
Author: loganfsmyth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-03 22:25:51

Dla przyszłych odniesień do innych na lookers, słyszę teraz, że zalecenie jest użycie WeakMaps do przechowywania prywatnych danych.

Oto bardziej przejrzysty, 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}`)
      }
    }
  }
}
 22
Author: Community,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:59

Zależy od kogo zapytasz :-)

Nie private modyfikator właściwości jest zawarty w Maximally minimal classes proposal which seems have made it into the current draft .

Jednak może istnieć wsparcie dla nazwy prywatne, co pozwala na własności prywatne - i prawdopodobnie mogą być używane również w definicjach klas.

 12
Author: Bergi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-03 22:20:11

Używanie modułów ES6 (początkowo zaproponowanych przez @d13) działa mi dobrze. Nie naśladuje doskonale prywatnych właściwości, ale przynajmniej możesz być pewien, ż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');
  }
};

Wtedy kod może wyglądać tak:

import Something from './something.js';

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

Aktualizacja (Ważna):

Jak zauważył @DanyalAytekin w komentarzach, te prywatne właściwości są statyczne, a więc globalne. Oni działa dobrze podczas pracy z Singletonami, ale należy zwrócić uwagę na obiekty przejściowe. Rozszerzenie powyższego przykładu:

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
 10
Author: Johnny Oshika,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-27 00:24:17

Tak - możesz utworzyć właściwość enkapsulowaną , ale nie została ona wykonana z modyfikatorami dostępu (public|private), przynajmniej nie z ES6.

Oto prosty przykład, jak można to zrobić z ES6:

1 Utwórz klasę używając class word

2 wewnątrz konstruktora deklaruje zmienną o zasięgu blokowym używając let lub const słowa zarezerwowane - > ponieważ są one zakresem blokowym, nie mogą być dostępne z zewnątrz (encapsulated)

3 aby umożliwić niektóre kontrolki dostępu (setters|getters) do tych zmiennych Można zadeklarować metodę instancji wewnątrz konstruktora używając: this.methodName=function(){} składnia

"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:

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
 9
Author: Nikita Kurtin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-08 15:28:17

Completing @d13 and the comments by @ johnny-oshika and @DanyalAytekin:

Myślę, że w przykładzie podanym przez @ johnny-oshika możemy użyć funkcji normalnych zamiast funkcji strzałek, a następnie .bind z bieżącym obiektem plus _privates obiekt jako parametr current:

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, o których mogę myśleć:

  • możemy mieć metody prywatne (_greet i _updateMessage zachowywać się jak prywatne metody dopóki nie będziemy export referencjami)
  • chociaż nie znajdują się na prototypie, wyżej wymienione metody zapisują pamięć, ponieważ instancje są tworzone raz, poza klasą (w przeciwieństwie do definiowania ich w konstruktorze)
  • nie wyciekamy żadnych globali, ponieważ jesteśmy w module
  • możemy również mieć prywatne właściwości używając obiektu _privates

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

Running snippet można znaleźć tutaj: http://www.webpackbin.com/NJgI5J8lZ

 9
Author: efidiles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-24 18:04:14

Inne podejście do"prywatnych"

Zamiast walczyć z faktem, że prywatna widoczność jest obecnie niedostępna w ES6, zdecydowałem się na bardziej praktyczne podejście, które działa dobrze, jeśli Twoje IDE obsługuje JSDoc(np. Chodzi o to, aby wykorzystać @private tag . Jeśli chodzi o rozwój, IDE uniemożliwi ci dostęp do jakiegokolwiek prywatnego członka spoza swojej klasy. Działa całkiem dobrze dla mnie i było to naprawdę przydatne do ukrywania wewnętrznych metod, więc Funkcja auto-complete pokazuje mi, co klasa naprawdę chciała ujawnić. Oto przykład:

auto-complete pokazujące tylko publiczne rzeczy

 7
Author: Lucio Paiva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-01 20:27:25

Tyle egzotycznych rozwiązań! Zazwyczaj nie dbam o prywatność, więc używam "pseudo prywatności", Jak to jestpowiedziane tutaj . Ale jeśli dbać (jeśli są jakieś specjalne wymagania dla tego) 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

Kolejna możliwa implementacja funkcji (konstruktor) Job:

function Job(name){
  var impl = new jobImpl(name);
  this.do = time => impl.do(time),
  this.stop = time => impl.stop(time)
}
 7
Author: Sergey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-10 08:02:36

WeakMap

  • obsługiwane w IE11 (symbole nie są)
  • hard-private (rekwizyty używające symboli są soft-private ze względu na Object.getOwnPropertySymbols)
  • może wyglądać naprawdę czysto (w przeciwieństwie do zamknięć, które wymagają wszystkich właściwości 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 odniesienie 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 na czystszą w przykładzie.

 6
Author: kevlened,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-27 01:24:42

Osobiście podoba mi się propozycja operatora bind :: i wtedy połączyłbym to z rozwiązaniem @d13, o którym wspomniałem, ale na razie trzymaj się odpowiedzi @d13, gdzie używasz słowa kluczowego export dla swojej klasy i umieszczasz funkcje prywatne w module.

Jest jeszcze jedno rozwiązanie trudne, które nie zostało tutaj wymienione, które poniżej są bardziej funkcjonalne podejście i pozwoliłoby mu mieć wszystkie prywatne rekwizyty / metody w klasy.

Szeregowy.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.

 5
Author: Robin F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-09 18:24:46

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.

Jednakże, jeśli z jakiegoś powodu musisz uniemożliwić dostęp z Object.getOwnPropertySymbols(), metoda, której używałem, to dołączanie unikalnej, nie konfigurowalnej, nieliczalnej, nie zapisywalnej właściwości, która może być używana jako identyfikator właściwości do każdego obiektu na budowie (np. unikalna Symbol, jeśli nie masz jeszcze innej unikalnej właściwości jak id). Następnie zachowaj mapę "prywatnych" zmiennych każdego obiektu używając 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"

Potencjalna przewaga tego podejścia nad wykorzystaniem WeakMap jest szybszy czas dostępu , jeśli wydajność staje się problemem.

 5
Author: NanoWizard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-29 12:29:52

Natknąłem się na ten post, szukając najlepszych praktyk dla "prywatnych danych dla klas". Wspomniano, że kilka wzorców będzie miało problemy z wydajnością.

Przygotowałem kilka testów jsperf na podstawie 4 głównych wzorców 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, najlepsze wzorce były "prywatne dane za pomocą środowisk konstruktorów" i "prywatne dane za pomocą konwencji nazewnictwa". Jak dla mnie Safari dobrze wypadło na Weakmapie, ale Chrome nie tak dobrze.

Nie znam wpływu na pamięć, ale wzór dla "środowisk konstruktorów", który niektórzy ostrzegali, że będzie problemem z wydajnością, był bardzo wydajny.

The 4 basic wzory to:

Prywatne Dane przez środowiska 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 pomocą środowisk konstruktora 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();

Dane prywatne 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 przez 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();

Dane prywatne 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();
 5
Author: MarkM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-08 23:48:16

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

Wszystkie dane są prywatne

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: To jest co prawda 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);
   }
};
 4
Author: JSInitiate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-20 16:09:24

W rzeczywistości jest to możliwe przy użyciu symboli i Proxy. Używasz symboli w zakresie klasy i ustawiasz dwie pułapki w proxy: jedną dla prototypu klasy, aby odzwierciedlał.ownKeys (instancja) lub obiekt.getOwnPropertySymbols nie oddaje Twoich symboli, drugi jest przeznaczony dla samego konstruktora, więc gdy wywołane jest new ClassName(attrs), zwracana instancja zostanie przechwycona i zablokowana. 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.

 4
Author: Francisco Neto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-27 23:12:13

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

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

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

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

Ale na ich daje to:

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

Więc ich "prywatne" słowo kluczowe jest nieskuteczne.

 4
Author: Michael Franzl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-18 21:16:07

Przychodząc bardzo późno na tę imprezę, ale trafiłem na pytanie OP w poszukiwaniu więc... Tak, możesz mieć prywatne właściwości, zawijając deklarację klasy w Zamknięcie

Jest przykład jak mam prywatne metody w tym codepen . W poniższym fragmencie Klasa subskrybowana ma dwie funkcje "prywatne" process i processCallbacks. Wszelkie właściwości mogą być dodawane w ten sposób i są one utrzymywane w tajemnicy poprzez wykorzystanie zamknięcia. IMO prywatność jest rzadką potrzebą, jeśli obawy są dobrze rozdzielone i Javascript nie musi być nadę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 oddziela obawy i utrzymuje rzeczy naprawdę prywatnie. Jedynym minusem jest potrzeba użycia "self" (lub czegoś podobnego), aby odnieść się do "tego" w treści prywatnej.

 4
Author: Paul Whipp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-07 05:45:21

Tak, całkowicie mogę, i całkiem łatwo też. Odbywa się to poprzez ujawnienie prywatnych zmiennych i funkcji przez zwrócenie prototypowego wykresu obiektu w konstruktorze. To nic nowego, ale weź trochę js foo, aby zrozumieć elegancję. W ten sposób nie używa się global scoped ani WeakMap. Jest to forma refleksji wbudowana w język. W zależności od tego, jak to wykorzystasz; można albo wymusić wyjątek, który przerywa stos wywołań, albo zakopać wyjątek jako undefined. To jest demonstarted poniżej, i można przeczytać więcej o tych funkcji 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
 4
Author: 1-14x0r,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-07 00:39:52
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
Author: Ilya Zarembsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-01 22:04:27

Inny sposób podobny do dwóch ostatnich postów

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
 3
Author: Jayesbe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-17 16:15:11

Czytając poprzednią odpowiedź pomyślałem, że ten przykład może podsumować powyższe rozwiązania

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

UPDATE

Teraz można tworzyć prawdziwe prywatne właściwości i metody(przynajmniej na razie w przeglądarkach chrome).

Składnia jest całkiem zgrabna

class MyClass {
    #privateProperty = 1
    #privateMethod() { return 2 }
    static #privateStatic = 3
    static #privateStaticMethod(){return 4}
    static get #privateStaticGetter(){return 5}

    // also using is quite straightforward
    method(){
        return (
            this.#privateMethod() +
            this.#privateProperty +
            MyClass.#privateStatic +
            MyClass.#privateStaticMethod() +
            MyClass.#privateStaticGetter
        )
    }
}

new MyClass().method()
// returns 15

Zauważ, że do pobierania statycznych referencji Nie użyłbyś this.constructor.#private, ponieważ to hamowałoby jego podklasy. Musisz użyć referencji do właściwej klasy, aby odzyskać jej statyczne referencje prywatne (które są dostępne tylko wewnątrz metod tej klasy), czyli MyClass.#private.

 3
Author: asdru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-08-21 16:06:57

Większość odpowiedzi mówi, że jest to niemożliwe lub wymaga użycia Weakmapy lub symbolu, które są funkcjami ES6, które prawdopodobnie wymagałyby polyfills. Jest jednak inny sposób! Zobacz też:

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

Wywołuję tę metodę accessor pattern . Podstawową ideą jest to, że mamy closure, key wewnątrz closure i tworzymy obiekt prywatny (w konstruktorze), do którego można uzyskać dostęp tylko wtedy, gdy masz klucz .

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

 2
Author: guitarino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-11 17:26:43

Zobacz Ta odpowiedź dla czystego i prostego rozwiązania "klasowego" z prywatnym i publicznym interfejsem i obsługą kompozycji

 2
Author: kofifus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-23 02:09:40

Znalazłem bardzo proste rozwiązanie, wystarczy użyć Object.freeze(). Oczywiście problem polega na tym, że nie można później nic dodać 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
 2
Author: Nikola Andreev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-07 07:48:26

Używam tego wzoru i zawsze mi się udało

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
Author: Yami Teru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-16 13:31:20

Właściwie to jest możliwe.
1. Najpierw Utwórz klasę i w konstruktorze zwróć wywołaną funkcję _public.
2. W wywołanej funkcji _public podaj referencję this (aby uzyskać dostęp do wszystkich prywatnych metod i właściwości) oraz wszystkie argumenty z constructor (które zostaną przekazane w new Names())
3. W zakresie funkcji _public znajduje się również klasa Names z dostępem do this (_this) referencji prywatnej Names klasy

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
Author: Paweł,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-24 05:22:54