Частная собственность в классах 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
,
Объект 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