Частная собственность в классах JavaScript ES6

Можно ли создавать частные свойства в классах ES6?

Вот пример. Как я могу предотвратить доступ к instance.property?

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

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

41 ответ

Решение

Ответ - нет". Но вы можете создать частный доступ к таким свойствам:

  • Используйте модули. Все в модуле является частным, если оно не стало общедоступным с помощью export ключевое слово.
  • Внутри модулей используйте функцию закрытия: http://www.kirupa.com/html5/closures_in_javascript.htm

(Предложение о том, что символы можно использовать для обеспечения конфиденциальности, было верным в более ранней версии спецификации ES6, но это уже не так: https://mail.mozilla.org/pipermail/es-discuss/2014-January/035604.html и /questions/35514145/kakova-motivatsiya-dlya-perenosa-simvolov-na-es6/35514157#35514157. Более подробное обсуждение символов и конфиденциальности см. по адресу: https://curiosity-driven.org/private-properties-in-javascript).

Частные поля внедряются в стандарте ECMA. Вы можете начать использовать их сегодня с предустановкой babel 7 и stage 3. Смотрите пример Babel REPL.

class Something {
  #property;

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

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

Короткий ответ: нет, в классах ES6 нет встроенной поддержки частных свойств.

Но вы можете имитировать это поведение, не присоединяя новые свойства к объекту, а сохраняя их внутри конструктора класса, и используйте методы получения и установки для доступа к скрытым свойствам. Обратите внимание, что методы получения и установки получают переопределение для каждого нового экземпляра класса.

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

Чтобы расширить ответ @loganfsmyth:

Единственные действительно личные данные в JavaScript - это переменные в области видимости. Вы не можете иметь частные свойства в том смысле, что к свойствам обращаются изнутри так же, как к открытым свойствам, но вы можете использовать переменные области действия для хранения личных данных.

Переменные области

Подход здесь состоит в том, чтобы использовать область функции конструктора, которая является частной, для хранения личных данных. Чтобы методы имели доступ к этим частным данным, они также должны быть созданы внутри конструктора, а это означает, что вы воссоздаете их с каждым экземпляром. Это снижение производительности и памяти, но некоторые считают, что это приемлемое ограничение. Наказания можно избежать за методы, которым не нужен доступ к частным данным, добавив их в прототип, как обычно.

Пример:

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

WeakMap может использоваться, чтобы избежать производительности предыдущего подхода и потери памяти. WeakMaps связывают данные с объектами (здесь, экземплярами) таким образом, что к ним можно получить доступ только с помощью этого WeakMap. Итак, мы используем метод переменных области видимости для создания приватного WeakMap, а затем используем этот WeakMap для извлечения приватных данных, связанных с this, Это быстрее, чем метод переменных области видимости, потому что все ваши экземпляры могут совместно использовать один WeakMap, поэтому вам не нужно пересоздавать методы просто для того, чтобы они получили доступ к своим собственным WeakMaps.

Пример:

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

В этом примере объект используется для использования одного WeakMap для нескольких частных свойств; Вы также можете использовать несколько WeakMaps и использовать их как age.set(this, 20)или напишите небольшую обертку и используйте ее по-другому, например privateProps.set(this, 'age', 0),

Конфиденциальность такого подхода теоретически может быть нарушена путем вмешательства в глобальную WeakMap объект. Тем не менее, весь JavaScript может быть сломан искаженными глобалами. Наш код уже построен на предположении, что этого не происходит.

(Этот метод также можно сделать с Map, но WeakMap лучше потому что Map создаст утечки памяти, если вы не будете очень осторожны, и для этого они ничем не отличаются.)

Полуответ: символы с ограничением

Символ - это тип примитивного значения, которое может служить именем свойства. Вы можете использовать метод переменной области видимости, чтобы создать частный символ, а затем сохранить личные данные в this[mySymbol],

Конфиденциальность этого метода может быть нарушена с помощью Object.getOwnPropertySymbols, но несколько неловко сделать.

Пример:

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

Полуответ: Подчеркивает

Старый по умолчанию, просто используйте публичное свойство с префиксом подчеркивания. Хотя это соглашение никоим образом не является частной собственностью, оно достаточно распространено, и оно хорошо справляется с тем, что читатели должны относиться к собственности как к частной, что часто выполняет свою работу. В обмен на это мы получаем подход, который легче читать, легче печатать и быстрее.

Пример:

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.

Заключение

Начиная с ES2017, до сих пор нет идеального способа сделать частную собственность. Различные подходы имеют свои плюсы и минусы. Переменные в области видимости являются действительно частными WeakMaps с областями видимости являются очень приватными и более практичными, чем переменные с областями видимости; Символы с определенной областью являются достаточно частными и достаточно практичными; подчеркивания часто бывают достаточно приватными и очень практичными.

Обновление: предложение с более приятным синтаксисом находится в процессе. Вклад приветствуется.


Да, есть - для ограниченного доступа в объектах - ES6 вводит Symbol с.

Символы уникальны, вы не можете получить доступ к одному извне, кроме как с помощью рефлексии (например, рядовых в Java/C#), но любой, кто имеет доступ к символу внутри, может использовать его для доступа к ключу:

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

Единственный способ получить истинную конфиденциальность в JS - это ограничить область действия, поэтому нет возможности иметь собственность, которая является членом this это будет доступно только внутри компонента. Лучший способ хранить действительно личные данные в ES6 - это 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));
  }
}

Очевидно, что это, вероятно, медленно, и определенно некрасиво, но это обеспечивает конфиденциальность.

Имейте в виду, что ДАЖЕ ЭТО не идеально, потому что Javascript настолько динамичен. Кто-то еще мог сделать

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

чтобы ловить значения по мере их хранения, поэтому, если вы хотите быть очень осторожным, вам нужно захватить локальную ссылку на .set а также .get использовать явно вместо того, чтобы полагаться на переопределяемый прототип.

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

Для дальнейшего ознакомления с другими пользователями я слышу, что рекомендуется использовать WeakMaps для хранения личных данных.

Вот более понятный рабочий пример:

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

Зависит от того, кого ты спрашиваешь:-)

нет private модификатор свойства включен в предложение " Максимально минимальные классы", которое, похоже, внесено в текущий черновик.

Тем не менее, может существовать поддержка частных имен, что допускает закрытые свойства - и они, вероятно, также могут использоваться в определениях классов.

Использование модулей ES6 (первоначально предложенных @d13) хорошо работает для меня. Он не полностью имитирует частные свойства, но, по крайней мере, вы можете быть уверены, что свойства, которые должны быть частными, не просочатся за пределы вашего класса. Вот пример:

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

Тогда код потребления может выглядеть так:

import Something from './something.js';

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

Обновление (важно):

Как отметил @DanyalAytekin в комментариях, эти частные свойства являются статическими, поэтому они имеют глобальный охват. Они будут хорошо работать при работе с синглетонами, но нужно соблюдать осторожность для временных объектов. Расширяя пример выше:

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

Другой подход к "частному"

Вместо того, чтобы бороться с тем фактом, что частная видимость в настоящее время недоступна в ES6, я решил использовать более практичный подход, который прекрасно работает, если ваша IDE поддерживает JSDoc (например, Webstorm). Идея состоит в том, чтобы использовать @private тег. Что касается разработки, IDE не позволит вам получить доступ к любому частному члену вне его класса. Для меня это работает очень хорошо, и это действительно полезно для сокрытия внутренних методов, поэтому функция автозаполнения показывает мне, что на самом деле хотел показать класс. Вот пример:

автозаполнение показывает только общедоступные вещи

Завершение @d13 и комментарии @johnny-oshika и @DanyalAytekin:

Я думаю, что в примере, представленном @johnny-oshika, мы могли бы использовать обычные функции вместо функций стрелок, а затем .bind их с текущим объектом плюс _privates объект как параметр карри:

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

Преимущества, которые я могу придумать:

  • у нас могут быть частные методы (_greet а также _updateMessage действовать как частные методы, пока мы не делаем export ссылки)
  • хотя они не относятся к прототипу, вышеупомянутые методы сохранят память, потому что экземпляры создаются один раз, вне класса (в отличие от определения их в конструкторе)
  • мы не пропускаем глобалы, так как мы внутри модуля
  • мы также можем иметь частные свойства, используя связанный _privates объект

Некоторые недостатки, которые я могу вспомнить:

  • менее интуитивно понятный
  • смешанное использование синтаксиса классов и шаблонов старой школы (привязки объектов, переменные области модуля / функции)
  • жесткие привязки - мы не можем перепривязать общедоступные методы (хотя мы можем улучшить это, используя мягкие привязки ( https://github.com/getify/You-Dont-Know-JS/blob/master/this%20%26%20object%20prototypes/ch2.md#softening-binding))

Работающий фрагмент можно найти здесь: http://www.webpackbin.com/NJgI5J8lZ

Да - вы можете создать инкапсулированное свойство, но это не было сделано с помощью модификаторов доступа (public|private), по крайней мере, с ES6.

Вот простой пример того, как это можно сделать с ES6:

1 Создайте класс, используя слово класса

2 Внутри его конструктора объявите переменную в области блока, используя let OR const зарезервированные слова ->, поскольку они являются областью видимости блока, к ним нельзя получить доступ извне (инкапсулировано)

3 Чтобы разрешить некоторый контроль доступа (setters|getters) к этим переменным, вы можете объявить метод экземпляра внутри его конструктора, используя: this.methodName=function(){} синтаксис

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

Теперь давайте проверим это:

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

О, так много экзотических решений! Меня обычно не волнует конфиденциальность, поэтому я использую "псевдо-конфиденциальность", как здесь сказано. Но если все равно (если для этого есть особые требования), я использую что-то вроде этого:

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

Еще одна возможная реализация функции (конструктор) Job:

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

WeakMap

  • поддерживается в IE11 (символы нет)
  • hard-private (реквизиты, использующие символы, являются soft-private из-за Object.getOwnPropertySymbols)
  • может выглядеть действительно чисто (в отличие от замыканий, которые требуют всех подпорок и методов в конструкторе)

Сначала определим функцию для переноса WeakMap:

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

Затем создайте ссылку за пределами вашего класса:

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

Примечание: класс не поддерживается IE11, но в примере он выглядит чище.

Я наткнулся на этот пост, когда искал лучшую практику для "личных данных для классов". Было упомянуто, что у некоторых шаблонов будут проблемы с производительностью.

Я собрал несколько тестов jsperf на основе 4 основных шаблонов из онлайн-книги "Исследование ES6":

http://exploringjs.com/es6/ch_classes.html

Тесты можно найти здесь:

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

В Chrome 63.0.3239 / Mac OS X 10.11.6 наиболее эффективными шаблонами были "Личные данные через среды конструктора" и "Личные данные через соглашение об именах". Для меня Safari показала хорошие результаты для WeakMap, но Chrome не так хорошо.

Я не знаю влияния на память, но шаблон для "среды конструктора", который некоторые предупреждали, будет проблемой производительности, был очень производительным.

4 основных шаблона:

Частные данные в среде конструктора

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

Личные данные в среде конструктора 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();

Личные данные через соглашение об именах

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

Личные данные через 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();

Личные данные через символы

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

Лично мне нравится предложение оператора привязки :: а затем объединить его с упомянутым решением @d13, но пока придерживайтесь ответа @d13, где вы используете export Ключевое слово для вашего класса и положить частные функции в модуле.

есть еще одно жесткое решение, которое не было упомянуто здесь, а это более функциональный подход, который позволил бы иметь все частные методы / методы в классе.

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

комментарии по этому поводу будут оценены.

Я думаю, что ответ Бенджамина, вероятно, является лучшим в большинстве случаев, пока язык не поддерживает явно частные переменные.

Тем не менее, если по какой-либо причине вам нужно предотвратить доступ с Object.getOwnPropertySymbols() метод, который я рассмотрел, использует присоединение уникального, неконфигурируемого, не перечисляемого, недоступного для записи свойства, которое можно использовать в качестве идентификатора свойства для каждого объекта в конструкции (такого как уникальный Symbol, если у вас еще нет другого уникального свойства, такого как id). Затем просто сохраните карту "частных" переменных каждого объекта, используя этот идентификатор.

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"

Потенциальное преимущество этого подхода перед использованием WeakMap быстрее время доступа, если производительность становится проблемой.

Я считаю, что можно получить "лучшее из обоих миров", используя замыкания внутри конструкторов. Есть два варианта:

Все данные члены являются частными

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

Некоторые участники являются частными

ПРИМЕЧАНИЕ: это по общему признанию некрасиво. Если вы знаете лучшее решение, отредактируйте этот ответ.

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

На самом деле это возможно с помощью символов и прокси. Вы используете символы в области видимости класса и устанавливаете две ловушки в прокси: одну для прототипа класса, чтобы Reflect.ownKeys (instance) или Object.getOwnPropertySymbols не передавали ваши символы, а другая - для самого конструктора. так когда new ClassName(attrs) вызывается, возвращенный экземпляр будет перехвачен и будет заблокирован символы собственных свойств. Вот код:

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() работает так: Object.getOwnPropertyNames(myObj).concat(Object.getOwnPropertySymbols(myObj)) Вот почему нам нужна ловушка для этих объектов.

Пришел очень поздно на эту вечеринку, но я задал вопрос ОП в поиске, так что...Да, вы можете иметь частные свойства, заключив объявление класса в закрытие

Есть пример того, как у меня есть частные методы в этом коде. В приведенном ниже фрагменте класс Subscribeable имеет две "приватные" функции process а также processCallbacks, Любые свойства могут быть добавлены таким образом, и они остаются закрытыми благодаря использованию замыкания. Конфиденциальность IMO является редкой необходимостью, если проблемы хорошо разделены, и Javascript не нужно раздуваться, добавляя больше синтаксиса, когда замыкание аккуратно делает свою работу.

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

Мне нравится такой подход, потому что он хорошо разделяет проблемы и сохраняет конфиденциальность. Единственным недостатком является необходимость использовать "я" (или что-то подобное), чтобы ссылаться на "это" в частном контенте.

Да, вполне может, и довольно легко тоже. Это делается путем предоставления ваших личных переменных и функций путем возврата графа прототипа объекта в конструктор. В этом нет ничего нового, но потратьте немного js foo, чтобы понять его элегантность. Этот способ не использует глобальные области видимости или слабые карты. Это форма отражения, встроенная в язык. В зависимости от того, как вы используете это; можно либо вызвать исключение, которое прерывает стек вызовов, либо похоронить исключение как undefined, Это показано ниже, и вы можете узнать больше об этих функциях здесь

class Clazz {
  constructor() {
    var _level = 1

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

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

var clazz = new Clazz();

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

Даже Typescript не может этого сделать. Из их документации:

Когда член помечен как закрытый, к нему нельзя получить доступ извне его содержащего класса. Например:

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

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

Но переносится на их площадку это дает:

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

Так что их "личное" ключевое слово неэффективно.

Еще один способ, похожий на последние два опубликованных

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

Я нашел очень простое решение, просто используйте Object.freeze(), Конечно, проблема в том, что вы не можете ничего добавить к объекту позже.

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

Читая предыдущий ответ, я подумал, что этот пример может суммировать вышеупомянутые решения

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

Этот код демонстрирует частные и общедоступные, статические и нестатические, переменные, методы и свойства на уровне экземпляра и класса.

https://codesandbox.io/s/class-demo-837bj

class Animal {
    static count = 0 // class static public
    static #ClassPriVar = 3 // class static private

    constructor(kind) {
        this.kind = kind // instance public property
        Animal.count++
        let InstancePriVar = 'InstancePriVar: ' + kind // instance private constructor-var
        log(InstancePriVar)
        Animal.#ClassPriVar += 3
        this.adhoc = 'adhoc' // instance public property w/out constructor- parameter
    }

    #PawCount = 4 // instance private var

    set Paws(newPawCount) {
        // instance public prop
        this.#PawCount = newPawCount
    }

    get Paws() {
        // instance public prop
        return this.#PawCount
    }

    get GetPriVar() {
        // instance public prop
        return Animal.#ClassPriVar
    }

    static get GetPriVarStat() {
        // class public prop
        return Animal.#ClassPriVar
    }

    PrintKind() {
        // instance public method
        log('kind: ' + this.kind)
    }

    ReturnKind() {
        // instance public function
        return this.kind
    }

    /* May be unsupported

    get #PrivMeth(){  // instance private prop
        return Animal.#ClassPriVar + ' Private Method'
    }

    static get #PrivMeth(){  // class private prop
        return Animal.#ClassPriVar + ' Private Method'
    }
    */
}

function log(str) {
    console.log(str)
}

// TESTING

log(Animal.count) // static, avail w/out instance
log(Animal.GetPriVarStat) // static, avail w/out instance

let A = new Animal('Cat')
log(Animal.count + ': ' + A.kind)
log(A.GetPriVar)
A.PrintKind()
A.Paws = 6
log('Paws: ' + A.Paws)
log('ReturnKind: ' + A.ReturnKind())
log(A.adhoc)

let B = new Animal('Dog')
log(Animal.count + ': ' + B.kind)
log(B.GetPriVar)
log(A.GetPriVar) // returns same as B.GetPriVar. Acts like a class-level property, but called like an instance-level property. It's cuz non-stat fx requires instance.

log('class: ' + Animal.GetPriVarStat)

// undefined
log('instance: ' + B.GetPriVarStat) // static class fx
log(Animal.GetPriVar) // non-stat instance fx
log(A.InstancePriVar) // private
log(Animal.InstancePriVar) // private instance var
log('PawCount: ' + A.PawCount) // private. Use getter
/* log('PawCount: ' + A.#PawCount) // private. Use getter
log('PawCount: ' + Animal.#PawCount) // Instance and private. Use getter */

На самом деле это возможно.
1. Сначала создайте класс и в конструкторе верните вызванный_public функция.
2. В звонилке _public функция передатьthisссылка (чтобы получить доступ ко всем закрытым методам и реквизиту) и все аргументы из constructor(это будет передано в new Names())
3. В _public сфера действия есть также Names класс с доступом к this (_this) ссылка частного Names учебный класс

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

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

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

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

  }
  return new Names(..._arguments);
}

В классах можно использовать приватные методы, используя WeakMap,

Согласно веб-документам MDN:

Объект WeakMap представляет собой набор пар ключ / значение, в которых ключи являются только объектами, а значения могут быть произвольными значениями.

Ссылки на объекты в ключах хранятся слабо, что означает, что они являются целью сборки мусора (GC), если больше нет другой ссылки на объект.

И это пример создания Queue структура данных с закрытым членом _items который содержит массив.

const _items = new WeakMap();

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

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

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

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

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

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

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

Пример использования:

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

Частный член _items скрыт и не может быть замечен в свойствах или методах Queue объект:

Тем не менее, частный член _items в Queue объект может быть достигнут с помощью этого способа:

const anArray = _items.get(this);

Я использую этот шаблон, и он всегда работал для меня

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

Другие вопросы по тегам