Приватные свойства в классах JavaScript ES6
Заголовок: Возможно ли создать приватные свойства в классах ES6?
Описание проблемы:
Как можно запретить доступ к свойству instance.property
в классах ES6? Вот пример:
class Something {
constructor() {
this.property = "test";
}
}
var instance = new Something();
console.log(instance.property); // => "test"
Я хотел бы выяснить, существует ли способ сделать свойство property
приватным, чтобы его нельзя было получить из экземпляра класса.
5 ответ(ов)
Обновление: см. ответы других пользователей, это устарело.
Краткий ответ: нет, в 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; };
}
Ответ: "Нет". Однако вы можете создать приватный доступ к свойствам следующим образом:
- Используйте модули. Все в модуле является приватным, если не сделано публичным с помощью ключевого слова
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)
Единственный способ обеспечить настоящую приватность в JavaScript — это использование области видимости, поэтому невозможно создать свойство, которое будет членом this
и доступно только внутри компонента. Лучший способ хранения действительно приватных данных в ES6 — это использование WeakMap
.
const privateProp1 = new WeakMap();
const privateProp2 = new WeakMap();
class SomeClass {
constructor() {
privateProp1.set(this, "Я Приватный1");
privateProp2.set(this, "Я Приватный2");
this.publicVar = "Я публичный";
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){
// Сохранить 'this', 'key' и '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, "Я Приватный1");
WMSet.call(privateProp2, this, "Я Приватный2");
this.publicVar = "Я публичный";
this.publicMethod = () => {
console.log(WMGet.call(privateProp1, this), WMGet.call(privateProp2, this))
};
}
printPrivate() {
console.log(WMGet.call(privateProp1, this));
}
}
Таким образом, использование WeakMap
является хорошим подходом для обеспечения приватности в JavaScript, несмотря на некоторые его ограничения.
Использование 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('Солнечный день!');
something.say(); // Солнечный день!
something._message; // undefined
something._greet(); // исключение
Обновление (Важно):
Как отметил @DanyalAytekin в комментариях, эти приватные свойства статичны, следовательно, они глобальны по своей области видимости. Они хорошо работают при создании одиночек (Singleton), но необходимо быть осторожным с временными объектами. Вот расширенный пример:
import Something from './something.js';
import Something2 from './something.js';
const a = new Something('а');
a.say(); // а
const b = new Something('б');
b.say(); // б
const c = new Something2('в');
c.say(); // в
a.say(); // в
b.say(); // в
c.say(); // в
Таким образом, несмотря на использование ES6 модулей, вам нужно быть внимательным к контексту и области видимости ваших "приватных" свойств.
Дополнив предложения @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
// следующее не является публичным
console.log(something._greet === undefined); // true
console.log(something._privates === undefined); // true
console.log(something._updateMessage === undefined); // true
// еще один экземпляр, который не делит _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.
Альтернативы переменным классов в ES6
Может ли выражение (a == 1 && a == 2 && a == 3) когда-либо оцениться как истинное?
Как клонировать объект JavaScript, исключив один ключ?
Соответствуют ли 'Стрелочные функции' и 'Функции' или они взаимозаменяемы?
Добавление тега script в React/JSX