it-roy-ru.com

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

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

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

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

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

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

class Something {
  #property;

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

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

Короткий ответ: нет, в классах 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; }
}
228
MetalGodwin

Чтобы расширить ответ @ 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 создаст утечки памяти, если вы не будете очень осторожны, и для этих целей они ничем иным не отличаются.)

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

Символ - это тип примитивного значения, которое может служить именем свойства. Вы можете использовать метод переменной области видимости для создания частного Symbol, а затем хранить личные данные в 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 с областями видимости являются очень закрытыми и более практичными, чем переменные с областями видимости; Символы с определенной областью являются достаточно частными и достаточно практичными; подчеркивания часто бывают достаточно приватными и очень практичными.

159
twhb

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


Да, для доступа к областям в объектах - ES6 вводит Symbols .

Символы уникальны, вы не можете получить доступ к одному извне, кроме как с помощью рефлексии (как рядовые в 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
111
Benjamin Gruenbaum

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

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

(Предложение о том, что символы можно использовать для обеспечения конфиденциальности, было верным в более ранней версии спецификации ES6, но это уже не так: https://mail.mozilla.org/pipermail/es-discuss/2014-January/ 035604.html и https://stackoverflow.com/a/22280202/1282216 . Более подробное обсуждение символов и конфиденциальности см. По адресу: https://curiosity-driven.org/private-properties- in-javascript )

28
d13

Единственный способ получить истинную конфиденциальность в 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));
  }
}
26
loganfsmyth

Для дальнейшего ознакомления с информацией о посетителях теперь я слышу, что рекомендуется использовать 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}`)
      }
    }
  }
}
21
user1076821

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

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

Однако может быть поддержкаprivate names , которая допускает закрытые свойства - и они, вероятно, также могут использоваться в определениях классов.

11
Bergi

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

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

thing.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 объект

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

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

9
efidiles

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

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

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

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
8
Nikita Kurtin

Использование модулей 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
8
Johnny Oshika

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

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

 auto-complete showing just public stuff

6
Lucio Paiva

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

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

6
kevlened

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

Однако, если по какой-то причине вам необходимо запретить доступ с помощью 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 - более быстрое время доступа , если производительность становится проблемой.

4
NanoWizard

Лично мне нравится предложение оператора bind::, а затем я бы объединил его с упомянутым решением @ 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

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

4
Robin F.

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

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

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

4
JSInitiate

На самом деле это возможно с помощью символов и прокси. Вы используете символы в области видимости класса и устанавливаете две ловушки в прокси: одну для прототипа класса, чтобы 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)), поэтому нам нужна ловушка для этих объектов.

4
Francisco Neto

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

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

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

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

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();
3
MarkM
class Something {
  constructor(){
    var _property = "test";
    Object.defineProperty(this, "property", {
        get: function(){ return _property}
    });
  }
}

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

Даже 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);

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

3
Michael Franzl

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

Есть пример того, как у меня есть закрытые методы в этот codepen . В приведенном ниже фрагменте класс 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)}}}})();

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

3
Paul Whipp

Я нашел очень простое решение, просто используйте 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
2
Nikola Andreev

Смотрите этот ответ для чистого и простого «классного» решения с закрытым и открытым интерфейсом и поддержкой композиции

2
kofifus

Да, вполне может, и довольно легко тоже. Это делается путем предоставления ваших личных переменных и функций путем возврата графа прототипа объекта в конструктор. В этом нет ничего нового, но потратьте немного 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

2
1-14x0r

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

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

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

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

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

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



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

2
Yami Teru

На самом деле это это возможно.
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);
}
2
Paweł

В классах можно использовать приватные методы, используя 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:

 enter image description here

Однако закрытый член _items в объекте Queue может быть достигнут следующим образом:

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

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

class Example {
  constructor(foo) {

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

    // public interface
    return self.public;
  }

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

  // everything else private
  bar = 10
}

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

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

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)
}
1
Sergey

Здесь переменная myThing является закрытой и является частью замыкания:

class Person {
  constructor() {

    var myThing = "Hello World";

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

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

var person = new Person();

console.log(person);
1
Naga Chaitanya Konada

Вы можете попробовать это https://www.npmjs.com/package/private-members

Этот пакет сохранит участников по экземпляру.

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

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

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

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

    return Exemplo;
})();

module.exports = Exemplo;
1
João Henrique

Большинство ответов либо говорят, что это невозможно, либо требуют использования WeakMap или Symbol, которые являются функциями ES6, для которых, вероятно, потребуются полифилы. Однако есть и другой способ! Проверьте это:

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

Я называю этот метод accessor pattern. Основная идея заключается в том, что у нас есть закрытие, ключ внутри замыкания, и мы создаем закрытый объект (в конструкторе), к которому можно получить доступ только если у вас есть ключ.

Если вы заинтересованы, вы можете прочитать больше об этом в моя статья . Используя этот метод, вы можете создавать для каждого объекта свойства, к которым нельзя получить доступ за пределами замыкания. Следовательно, вы можете использовать их в конструкторе или прототипе, но не где-либо еще. Я нигде не видел, чтобы этот метод использовался, но я думаю, что он действительно мощный.

1
guitarino

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

Ниже приведено только то, что я использую (может быть полезно). По сути, я оборачиваю класс внутри фабрики.

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

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

Я новичок так рад слышать, если это плохой подход.

0
Alexander Sharikov

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

const friend = Symbol('friend');

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

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

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

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

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

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

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

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

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

Я разработал модуль, который помогает вам использовать ограничение доступа в классе JavaScript под названием Capsulable. (Частный и защищенный статический)

Если вам интересно, посмотрите мой пакет ниже . https://github.com/hmmhmmhm/capsulable

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

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

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

module.exports = A
0
hmmhmmhm